// Copyright (c) 2018 Google Inc.
// Modifications Copyright (C) 2024 Advanced Micro Devices, Inc. All rights
// reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Validation tests for memory/storage

#include <string>
#include <vector>

#include "gmock/gmock.h"
#include "test/unit_spirv.h"
#include "test/val/val_code_generator.h"
#include "test/val/val_fixtures.h"

// For pretty-printing tuples with spv_target_env.
std::ostream& operator<<(std::ostream& stream, spv_target_env target) {
  switch (target) {
    case SPV_ENV_UNIVERSAL_1_3:
      return stream << "SPV_ENV_UNIVERSAL_1_3";
    case SPV_ENV_UNIVERSAL_1_4:
      return stream << "SPV_ENV_UNIVERSAL_1_4";
    default:
      return stream << (unsigned)target;
  }
}

namespace spvtools {
namespace val {
namespace {

using ::testing::Combine;
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::Values;

using ValidateMemory = spvtest::ValidateBase<bool>;

TEST_F(ValidateMemory, VulkanUniformConstantOnNonOpaqueResourceBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer UniformConstant %float
%2 = OpVariable %float_ptr UniformConstant
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-UniformConstant-04655"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Variables identified with the UniformConstant storage class "
                "are used only as handles to refer to opaque resources. Such "
                "variables must be typed as OpTypeImage, OpTypeSampler, "
                "OpTypeSampledImage, OpTypeAccelerationStructureKHR, "
                "or an array of one of these types."));
}

TEST_F(ValidateMemory, VulkanUniformConstantOnOpaqueResourceGood) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
%sampler = OpTypeSampler
%sampler_ptr = OpTypePointer UniformConstant %sampler
%2 = OpVariable %sampler_ptr UniformConstant
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanUniformConstantOnNonOpaqueResourceArrayBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%uint = OpTypeInt 32 0
%array_size = OpConstant %uint 5
%array = OpTypeArray %float %array_size
%array_ptr = OpTypePointer UniformConstant %array
%2 = OpVariable %array_ptr UniformConstant
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-UniformConstant-04655"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Variables identified with the UniformConstant storage class "
                "are used only as handles to refer to opaque resources. Such "
                "variables must be typed as OpTypeImage, OpTypeSampler, "
                "OpTypeSampledImage, OpTypeAccelerationStructureKHR, "
                "or an array of one of these types."));
}

TEST_F(ValidateMemory, VulkanUniformConstantOnOpaqueResourceArrayGood) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
%sampler = OpTypeSampler
%uint = OpTypeInt 32 0
%array_size = OpConstant %uint 5
%array = OpTypeArray %sampler %array_size
%array_ptr = OpTypePointer UniformConstant %array
%2 = OpVariable %array_ptr UniformConstant
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanUniformConstantOnOpaqueResourceRuntimeArrayGood) {
  std::string spirv = R"(
OpCapability RuntimeDescriptorArrayEXT
OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
%sampler = OpTypeSampler
%uint = OpTypeInt 32 0
%array = OpTypeRuntimeArray %sampler
%array_ptr = OpTypePointer UniformConstant %array
%2 = OpVariable %array_ptr UniformConstant
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanUniformOnIntBad) {
  char src[] = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
            OpEntryPoint GLCompute %kernel "main"
            OpExecutionMode %kernel LocalSize 1 1 1

            OpDecorate %var DescriptorSet 0
            OpDecorate %var Binding 0

  %voidty = OpTypeVoid
%kernelty = OpTypeFunction %voidty
   %intty = OpTypeInt 32 0
   %varty = OpTypePointer Uniform %intty
   %value = OpConstant %intty 42

     %var = OpVariable %varty Uniform

  %kernel = OpFunction %voidty None %kernelty
   %label = OpLabel
            OpStore %var %value
            OpReturn
            OpFunctionEnd
)";
  CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("From Vulkan spec:\n"
                "Variables identified with the Uniform storage class are used "
                "to access transparent buffer backed resources. Such variables "
                "must be typed as OpTypeStruct, or an array of this type"));
}

// #version 440
// #extension GL_EXT_nonuniform_qualifier : enable
// layout(binding = 1) uniform sampler2D s2d[][2];
// layout(location = 0) in nonuniformEXT int i;
// void main()
// {
//     vec4 v = texture(s2d[i][i], vec2(0.3));
// }
TEST_F(ValidateMemory, VulkanUniformOnRuntimeArrayOfArrayBad) {
  char src[] = R"(
               OpCapability Shader
               OpCapability ShaderNonUniformEXT
               OpCapability RuntimeDescriptorArrayEXT
               OpCapability SampledImageArrayNonUniformIndexingEXT
               OpExtension "SPV_EXT_descriptor_indexing"
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %main "main" %i
               OpSource GLSL 440
               OpSourceExtension "GL_EXT_nonuniform_qualifier"
               OpName %main "main"
               OpName %v "v"
               OpName %s2d "s2d"
               OpName %i "i"
               OpDecorate %s2d DescriptorSet 0
               OpDecorate %s2d Binding 1
               OpDecorate %i Location 0
               OpDecorate %i NonUniformEXT
               OpDecorate %21 NonUniformEXT
               OpDecorate %22 NonUniformEXT
               OpDecorate %25 NonUniformEXT
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
    %v4float = OpTypeVector %float 4
%_ptr_Function_v4float = OpTypePointer Function %v4float
         %10 = OpTypeImage %float 2D 0 0 0 1 Unknown
         %11 = OpTypeSampledImage %10
       %uint = OpTypeInt 32 0
     %uint_2 = OpConstant %uint 2
%_arr_11_uint_2 = OpTypeArray %11 %uint_2
%_runtimearr__arr_11_uint_2 = OpTypeRuntimeArray %_arr_11_uint_2
%_ptr_Uniform__runtimearr__arr_11_uint_2 = OpTypePointer Uniform %_runtimearr__arr_11_uint_2
        %s2d = OpVariable %_ptr_Uniform__runtimearr__arr_11_uint_2 Uniform
        %int = OpTypeInt 32 1
%_ptr_Input_int = OpTypePointer Input %int
          %i = OpVariable %_ptr_Input_int Input
%_ptr_Uniform_11 = OpTypePointer Uniform %11
    %v2float = OpTypeVector %float 2
%float_0_300000012 = OpConstant %float 0.300000012
         %28 = OpConstantComposite %v2float %float_0_300000012 %float_0_300000012
    %float_0 = OpConstant %float 0
       %main = OpFunction %void None %3
          %5 = OpLabel
          %v = OpVariable %_ptr_Function_v4float Function
         %21 = OpLoad %int %i
         %22 = OpLoad %int %i
         %24 = OpAccessChain %_ptr_Uniform_11 %s2d %21 %22
         %25 = OpLoad %11 %24
         %30 = OpImageSampleExplicitLod %v4float %25 %28 Lod %float_0
               OpStore %v %30
               OpReturn
               OpFunctionEnd
)";
  CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("From Vulkan spec:\n"
                "Variables identified with the Uniform storage class are used "
                "to access transparent buffer backed resources. Such variables "
                "must be typed as OpTypeStruct, or an array of this type"));
}

// #version 440
// layout (set=1, binding=1) uniform sampler2D variableName[2][2];
// void main() {
// }
TEST_F(ValidateMemory, VulkanUniformOnArrayOfArrayBad) {
  char src[] = R"(
               OpCapability Shader
          %1 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel Logical GLSL450
               OpEntryPoint Vertex %main "main"
               OpSource GLSL 440
               OpName %main "main"
               OpName %variableName "variableName"
               OpDecorate %variableName DescriptorSet 1
               OpDecorate %variableName Binding 1
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
          %7 = OpTypeImage %float 2D 0 0 0 1 Unknown
          %8 = OpTypeSampledImage %7
       %uint = OpTypeInt 32 0
     %uint_2 = OpConstant %uint 2
%_arr_8_uint_2 = OpTypeArray %8 %uint_2
%_arr__arr_8_uint_2_uint_2 = OpTypeArray %_arr_8_uint_2 %uint_2
%_ptr_Uniform__arr__arr_8_uint_2_uint_2 = OpTypePointer Uniform %_arr__arr_8_uint_2_uint_2
%variableName = OpVariable %_ptr_Uniform__arr__arr_8_uint_2_uint_2 Uniform
       %main = OpFunction %void None %3
          %5 = OpLabel
               OpReturn
               OpFunctionEnd
)";
  CompileSuccessfully(src, SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("From Vulkan spec:\n"
                "Variables identified with the Uniform storage class are used "
                "to access transparent buffer backed resources. Such variables "
                "must be typed as OpTypeStruct, or an array of this type"));
}

TEST_F(ValidateMemory, MismatchingStorageClassesBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Uniform %float
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
%2 = OpVariable %float_ptr Function
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Storage class must match result type storage class"));
}

TEST_F(ValidateMemory, MatchingStorageClassesGood) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Function %float
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
%2 = OpVariable %float_ptr Function
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, VulkanInitializerWithOutputStorageClassesGood) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Output %float
%init_val = OpConstant %float 1.0
%1 = OpVariable %float_ptr Output %init_val
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanInitializerWithFunctionStorageClassesGood) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Function %float
%init_val = OpConstant %float 1.0
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%1 = OpLabel
%2 = OpVariable %float_ptr Function %init_val
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanInitializerWithPrivateStorageClassesGood) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Private %float
%init_val = OpConstant %float 1.0
%1 = OpVariable %float_ptr Private %init_val
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanInitializerWithDisallowedStorageClassesBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Input %float
%init_val = OpConstant %float 1.0
%1 = OpVariable %float_ptr Input %init_val
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpVariable-04651"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("OpVariable, <id> '5[%5]', has a disallowed initializer & "
                "storage class combination.\nFrom Vulkan spec:\nVariable "
                "declarations that include initializers must have one of the "
                "following storage classes: Output, Private, Function or "
                "Workgroup\n  %5 "
                "= OpVariable %_ptr_Input_float Input %float_1\n"));
}

TEST_F(ValidateMemory, UniversalInitializerWithDisallowedStorageClassesBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Input %float
%init_val = OpConstant %float 1.0
%1 = OpVariable %float_ptr Input %init_val
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpVariable, <id> '5[%5]', initializer are not allowed for Input"));
}

TEST_F(ValidateMemory, InitializerWithTaskPayloadWorkgroupEXT) {
  std::string spirv = R"(
               OpCapability MeshShadingEXT
               OpExtension "SPV_EXT_mesh_shader"
               OpMemoryModel Logical GLSL450
               OpEntryPoint TaskEXT %main "main" %payload
       %void = OpTypeVoid
       %func = OpTypeFunction %void
       %uint = OpTypeInt 32 0
%_ptr_TaskPayloadWorkgroupEXT = OpTypePointer TaskPayloadWorkgroupEXT %uint
     %uint_1 = OpConstant %uint 1
    %payload = OpVariable %_ptr_TaskPayloadWorkgroupEXT TaskPayloadWorkgroupEXT %uint_1
       %main = OpFunction %void None %func
      %label = OpLabel
               OpReturn
               OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_5);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpVariable, <id> '2[%2]', initializer are not allowed "
                        "for TaskPayloadWorkgroupEXT"));
}

TEST_F(ValidateMemory, ArrayLenCorrectResultType) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %uint = OpTypeInt 32 0
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %_runtimearr_float
%_ptr_Function__struct_7 = OpTypePointer Function %_struct_7
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function__struct_7 Function
         %11 = OpArrayLength %uint %10 0
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, ArrayLenIndexCorrectWith2Members) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %uint = OpTypeInt 32 0
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %float %_runtimearr_float
%_ptr_Function__struct_7  = OpTypePointer Function %_struct_7
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function__struct_7  Function
         %11 = OpArrayLength %uint %10 1
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, ArrayLenResultNotIntType) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_6 = OpTypeStruct %_runtimearr_float
%_ptr_Function__struct_6 = OpTypePointer Function %_struct_6
          %1 = OpFunction %void None %3
          %8 = OpLabel
          %9 = OpVariable %_ptr_Function__struct_6 Function
         %10 = OpArrayLength %float %9 0
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "The Result Type of OpArrayLength <id> '10[%10]' must be OpTypeInt "
          "with width 32 or 64 and signedness 0.\n  %10 = OpArrayLength %float "
          "%9 "
          "0\n"));
}

TEST_F(ValidateMemory, ArrayLenResultNot32bits) {
  std::string spirv = R"(
               OpCapability Shader
               OpCapability Int16
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %ushort = OpTypeInt 16 0
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %_runtimearr_float
%_ptr_Function__struct_7 = OpTypePointer Function %_struct_7
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function__struct_7 Function
         %11 = OpArrayLength %ushort %10 0
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "The Result Type of OpArrayLength <id> '11[%11]' must be OpTypeInt "
          "with width 32 or 64 and signedness 0.\n  %11 = OpArrayLength "
          "%ushort %10 "
          "0\n"));
}

TEST_F(ValidateMemory, ArrayLenResultSigned) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %int = OpTypeInt 32 1
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %_runtimearr_float
%_ptr_Function__struct_7 = OpTypePointer Function %_struct_7
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function__struct_7 Function
         %11 = OpArrayLength %int %10 0
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "The Result Type of OpArrayLength <id> '11[%11]' must be OpTypeInt "
          "with width 32 or 64 and signedness 0.\n  %11 = OpArrayLength %int "
          "%10 "
          "0\n"));
}

TEST_F(ValidateMemory, ArrayLenInputNotStruct) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %uint = OpTypeInt 32 0
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %_runtimearr_float
%_ptr_Function_float = OpTypePointer Function %float
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function_float Function
         %11 = OpArrayLength %uint %10 0
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("The Structure's type in OpArrayLength <id> '11[%11]' "
                        "must be a pointer to an OpTypeStruct."));
}

TEST_F(ValidateMemory, ArrayLenInputLastMemberNoRTA) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %uint = OpTypeInt 32 0
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %float
%_ptr_Function__struct_7  = OpTypePointer Function %_struct_7
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function__struct_7  Function
         %11 = OpArrayLength %uint %10 0
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The Structure's last member in OpArrayLength <id> '11[%11]' "
                "must be an OpTypeRuntimeArray.\n  %11 = OpArrayLength %uint "
                "%10 0\n"));
}

TEST_F(ValidateMemory, ArrayLenInputLastMemberNoRTA2) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %uint = OpTypeInt 32 0
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %_runtimearr_float %float
%_ptr_Function__struct_7  = OpTypePointer Function %_struct_7
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function__struct_7  Function
         %11 = OpArrayLength %uint %10 1
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The Structure's last member in OpArrayLength <id> '11[%11]' "
                "must be an OpTypeRuntimeArray.\n  %11 = OpArrayLength %uint "
                "%10 1\n"));
}

TEST_F(ValidateMemory, ArrayLenIndexNotLastMember) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %uint = OpTypeInt 32 0
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %float %_runtimearr_float
%_ptr_Function__struct_7  = OpTypePointer Function %_struct_7
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function__struct_7  Function
         %11 = OpArrayLength %uint %10 0
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "The array member in OpArrayLength <id> '11[%11]' must be the "
          "last member of the struct.\n  %11 = OpArrayLength %uint %10 0\n"));
}

TEST_F(ValidateMemory, ArrayLenIndexNotPointerToStruct) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
     %uint = OpTypeInt 32 0
%_runtimearr_float = OpTypeRuntimeArray %float
  %_struct_7 = OpTypeStruct %float
%_ptr_Function__struct_7  = OpTypePointer Function %_struct_7
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %10 = OpVariable %_ptr_Function__struct_7  Function
         %11 = OpLoad %_struct_7 %10
         %12 = OpArrayLength %uint %11 0
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "The Structure's type in OpArrayLength <id> '12[%12]' must be a "
          "pointer to an OpTypeStruct.\n  %12 = OpArrayLength %uint %11 0\n"));
}

TEST_F(ValidateMemory, ArrayLenPointerIsAType) {
  std::string spirv = R"(
               OpCapability Shader
               OpMemoryModel Logical GLSL450
               OpEntryPoint Fragment %1 "main"
               OpExecutionMode %1 OriginUpperLeft
       %void = OpTypeVoid
          %3 = OpTypeFunction %void
      %float = OpTypeFloat 32
       %uint = OpTypeInt 32 0
          %1 = OpFunction %void None %3
          %9 = OpLabel
         %12 = OpArrayLength %uint %float 0
               OpReturn
               OpFunctionEnd

)";

  CompileSuccessfully(spirv.c_str());
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Operand '4[%float]' cannot be a "
                        "type"));
}

TEST_F(ValidateMemory, PushConstantNotStructGood) {
  std::string spirv = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
            OpEntryPoint Fragment %1 "main"
            OpExecutionMode %1 OriginUpperLeft

    %void = OpTypeVoid
  %voidfn = OpTypeFunction %void
   %float = OpTypeFloat 32
     %ptr = OpTypePointer PushConstant %float
      %pc = OpVariable %ptr PushConstant

       %1 = OpFunction %void None %voidfn
   %label = OpLabel
            OpReturn
            OpFunctionEnd
)";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, VulkanPushConstantNotStructBad) {
  std::string spirv = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
            OpEntryPoint Fragment %1 "main"
            OpExecutionMode %1 OriginUpperLeft

    %void = OpTypeVoid
  %voidfn = OpTypeFunction %void
   %float = OpTypeFloat 32
     %ptr = OpTypePointer PushConstant %float
      %pc = OpVariable %ptr PushConstant

       %1 = OpFunction %void None %voidfn
   %label = OpLabel
            OpReturn
            OpFunctionEnd
)";
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PushConstant-06808"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("PushConstant OpVariable <id> '6[%6]' has illegal "
                "type.\nFrom Vulkan spec, Push Constant Interface section:\n"
                "Such variables must be typed as OpTypeStruct"));
}

TEST_F(ValidateMemory, VulkanPushConstantArrayOfStructBad) {
  std::string spirv = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
            OpEntryPoint Fragment %1 "main"
            OpExecutionMode %1 OriginUpperLeft

            OpDecorate %struct Block
            OpMemberDecorate %struct 0 Offset 0

    %void = OpTypeVoid
  %voidfn = OpTypeFunction %void
   %float = OpTypeFloat 32
     %int = OpTypeInt 32 0
   %int_1 = OpConstant %int 1
  %struct = OpTypeStruct %float
   %array = OpTypeArray %struct %int_1
     %ptr = OpTypePointer PushConstant %array
      %pc = OpVariable %ptr PushConstant

       %1 = OpFunction %void None %voidfn
   %label = OpLabel
            OpReturn
            OpFunctionEnd
)";
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PushConstant-06808"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("PushConstant OpVariable <id> '10[%10]' has illegal "
                "type.\nFrom Vulkan spec, Push Constant Interface section:\n"
                "Such variables must be typed as OpTypeStruct"));
}

TEST_F(ValidateMemory, VulkanPushConstant) {
  std::string spirv = R"(
            OpCapability Shader
            OpMemoryModel Logical GLSL450
            OpEntryPoint Fragment %1 "main"
            OpExecutionMode %1 OriginUpperLeft

            OpDecorate %struct Block
            OpMemberDecorate %struct 0 Offset 0

    %void = OpTypeVoid
  %voidfn = OpTypeFunction %void
   %float = OpTypeFloat 32
  %struct = OpTypeStruct %float
     %ptr = OpTypePointer PushConstant %struct
      %pc = OpVariable %ptr PushConstant

       %1 = OpFunction %void None %voidfn
   %label = OpLabel
            OpReturn
            OpFunctionEnd
)";
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadBad1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
%load = OpLoad %int %var MakePointerVisibleKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadBad2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
%load = OpLoad %int %var Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadGood1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
%load = OpLoad %int %var MakePointerVisibleKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeLoadGood2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
%load = OpLoad %int %var Aligned|MakePointerVisibleKHR|NonPrivatePointerKHR 4 %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreBad1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpStore %var %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreBad2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpStore %var %device Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreGood1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpStore %var %device MakePointerAvailableKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeStoreGood2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpStore %var %device Aligned|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 MakePointerAvailableKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%workgroup = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryBad3) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%workgroup = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%workgroup = OpConstant %int 2
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemoryGood3) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%workgroup = OpConstant %int 2
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessAvVisBadBinaryV13) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
  MakePointerAvailableKHR|NonPrivatePointerKHR %device
  MakePointerVisibleKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "with two memory access operands requires SPIR-V 1.4 or later"));
}

TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessAvVisGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
  MakePointerAvailableKHR|NonPrivatePointerKHR %device
  MakePointerVisibleKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(), Eq(""));
}

TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessFirstWithAvBad) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
  MakePointerAvailableKHR|NonPrivatePointerKHR %device
  MakePointerAvailableKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Source memory access must not include MakePointerAvailableKHR\n"
          "  OpCopyMemory %5 %6 MakePointerAvailable|NonPrivatePointer"
          " %uint_1 MakePointerAvailable|NonPrivatePointer %uint_1"));
}

TEST_F(ValidateMemory, VulkanMemoryModelCopyMemoryTwoAccessSecondWithVisBad) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
  MakePointerVisibleKHR|NonPrivatePointerKHR %device
  MakePointerVisibleKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Target memory access must not include MakePointerVisibleKHR\n"
                "  OpCopyMemory %5 %6 MakePointerVisible|NonPrivatePointer"
                " %uint_1 MakePointerVisible|NonPrivatePointer %uint_1"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpCapability Addresses
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_4 MakePointerAvailableKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpCapability Addresses
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%device = OpConstant %int 1
%workgroup = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_4 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedBad3) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability Linkage
OpCapability Addresses
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%device = OpConstant %int 1
%workgroup = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_4 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Use of device scope with VulkanKHR memory model requires the "
                "VulkanMemoryModelDeviceScopeKHR capability"));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpCapability Addresses
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%device = OpConstant %int 1
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_4 MakePointerAvailableKHR|NonPrivatePointerKHR %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpCapability Addresses
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%device = OpConstant %int 1
%workgroup = OpConstant %int 2
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_4 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %device %workgroup
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanMemoryModelDeviceScopeCopyMemorySizedGood3) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Linkage
OpCapability Addresses
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%device = OpConstant %int 1
%workgroup = OpConstant %int 2
%int_ptr_ssbo = OpTypePointer StorageBuffer %int
%var1 = OpVariable %int_ptr_ssbo StorageBuffer
%var2 = OpVariable %int_ptr_ssbo StorageBuffer
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_4 Aligned|MakePointerVisibleKHR|MakePointerAvailableKHR|NonPrivatePointerKHR 4 %workgroup %device
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, ArrayLengthStructIsLabel) {
  const std::string spirv = R"(
OpCapability Tessellation
OpMemoryModel Logical GLSL450
OpName %20 "incorrect"
%void = OpTypeVoid
%3 = OpTypeFunction %void
%float = OpTypeFloat 32
%v4float = OpTypeVector %float 4
%uint = OpTypeInt 32 0
%4 = OpFunction %void None %3
%20 = OpLabel
%24 = OpArrayLength %uint %20 0
%25 = OpLoad %v4float %24
OpReturnValue %25
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Operand '1[%incorrect]' requires a type"));
}

TEST_F(ValidateMemory, PSBLoadAlignedSuccess) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
%val3 = OpLoad %uint64 %val2 Aligned 8
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
}

TEST_F(ValidateMemory, PSBLoadAlignedMissing) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
%val3 = OpLoad %uint64 %val2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
}

TEST_F(ValidateMemory, PSBLoadAlignedMissingWithOtherOperand) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
%val3 = OpLoad %uint64 %val2 Volatile
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
}

TEST_F(ValidateMemory, PSBStoreAlignedSuccess) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%u64_1 = OpConstant %uint64 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
OpStore %val2 %u64_1 Aligned 8
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
}

TEST_F(ValidateMemory, PSBStoreAlignedMissing) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%u64_1 = OpConstant %uint64 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
OpStore %val2 %u64_1 None
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
}

TEST_F(ValidateMemory, PSBStoreAlignedZero) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
OpStore %val2 %uint_1 Aligned 0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Memory accesses Aligned operand value 0 is not a power of two"));
}

TEST_F(ValidateMemory, PSBStoreAlignedNonPoT) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%uint = OpTypeInt 32 0
%uint_1 = OpConstant %uint 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
OpStore %val2 %uint_1 Aligned 3
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Memory accesses Aligned operand value 3 is not a power of two."));
}

TEST_F(ValidateMemory, PSBCopyMemoryAlignedSuccess) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%u64_1 = OpConstant %uint64 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
%val3 = OpLoad %ptr %val1
OpCopyMemory %val2 %val3 Aligned 8
OpCopyMemory %val3 %val2 Aligned 8 Aligned 8
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
}

TEST_F(ValidateMemory, PSBCopyMemoryAlignedMissingTarget) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%u64_1 = OpConstant %uint64 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
%val3 = OpLoad %ptr %val1
OpCopyMemory %val2 %val3 Volatile Aligned 8
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
}

TEST_F(ValidateMemory, PSBCopyMemoryAlignedMissingSource) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%u64_1 = OpConstant %uint64 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
%val3 = OpLoad %ptr %val1
OpCopyMemory %val2 %val3 Aligned 8 Volatile
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
}

TEST_F(ValidateMemory, PSBCopyMemoryAlignedMissingBoth) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%u64_1 = OpConstant %uint64 1
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%pptr_f = OpTypePointer Function %ptr
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
%val1 = OpVariable %pptr_f Function
%val2 = OpLoad %ptr %val1
%val3 = OpLoad %ptr %val1
OpCopyMemory %val2 %val3 Volatile
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-04708"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Memory accesses with PhysicalStorageBuffer must use Aligned"));
}

TEST_F(ValidateMemory, PSBVariable) {
  const std::string body = R"(
OpCapability PhysicalStorageBufferAddresses
OpCapability Int64
OpCapability Shader
OpExtension "SPV_EXT_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpDecorate %val1 AliasedPointer
%uint64 = OpTypeInt 64 0
%ptr = OpTypePointer PhysicalStorageBuffer %uint64
%val1 = OpVariable %ptr PhysicalStorageBuffer
%void = OpTypeVoid
%voidfn = OpTypeFunction %void
%main = OpFunction %void None %voidfn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("PhysicalStorageBuffer must not be used with OpVariable"));
}

TEST_F(ValidateMemory, PSBStoreAlignedOneWithUvec4) {
  const std::string body = R"(
               OpCapability Shader
               OpCapability PhysicalStorageBufferAddresses
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %main "main" %_
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %SSBO Block
               OpMemberDecorate %SSBO 0 Offset 0
               OpDecorate %Ptr Block
               OpMemberDecorate %Ptr 0 Offset 0
               OpDecorate %_ Binding 0
               OpDecorate %_ DescriptorSet 0
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_Ptr PhysicalStorageBuffer
       %SSBO = OpTypeStruct %_ptr_PhysicalStorageBuffer_Ptr
       %uint = OpTypeInt 32 0
     %v4uint = OpTypeVector %uint 4
        %Ptr = OpTypeStruct %v4uint
%_ptr_PhysicalStorageBuffer_Ptr = OpTypePointer PhysicalStorageBuffer %Ptr
%_ptr_StorageBuffer_SSBO = OpTypePointer StorageBuffer %SSBO
          %_ = OpVariable %_ptr_StorageBuffer_SSBO StorageBuffer
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_Ptr = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_Ptr
     %uint_0 = OpConstant %uint 0
         %20 = OpConstantComposite %v4uint %uint_0 %uint_0 %uint_0 %uint_0
%_ptr_PhysicalStorageBuffer_v4uint = OpTypePointer PhysicalStorageBuffer %v4uint
       %main = OpFunction %void None %4
          %6 = OpLabel
         %17 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_Ptr %_ %int_0
         %18 = OpLoad %_ptr_PhysicalStorageBuffer_Ptr %17
         %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_v4uint %18 %int_0
               OpStore %22 %20 Aligned 1
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-06314"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Memory accesses Aligned operand value 1 is too small, "
                        "the largest scalar type is 4 bytes"));
}

TEST_F(ValidateMemory, PSBStoreAlignedOneWithUint32) {
  const std::string body = R"(
               OpCapability Shader
               OpCapability PhysicalStorageBufferAddresses
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %main "main" %_
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %SSBO Block
               OpMemberDecorate %SSBO 0 Offset 0
               OpDecorate %B Block
               OpMemberDecorate %B 0 Offset 0
               OpDecorate %_ Binding 0
               OpDecorate %_ DescriptorSet 0
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_B PhysicalStorageBuffer
       %SSBO = OpTypeStruct %_ptr_PhysicalStorageBuffer_B
       %uint = OpTypeInt 32 0
          %B = OpTypeStruct %uint
%_ptr_PhysicalStorageBuffer_B = OpTypePointer PhysicalStorageBuffer %B
%_ptr_StorageBuffer_SSBO = OpTypePointer StorageBuffer %SSBO
          %_ = OpVariable %_ptr_StorageBuffer_SSBO StorageBuffer
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_B = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_B
     %uint_0 = OpConstant %uint 0
%_ptr_PhysicalStorageBuffer_uint = OpTypePointer PhysicalStorageBuffer %uint
       %main = OpFunction %void None %4
          %6 = OpLabel
         %16 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_B %_ %int_0
         %17 = OpLoad %_ptr_PhysicalStorageBuffer_B %16
         %20 = OpAccessChain %_ptr_PhysicalStorageBuffer_uint %17 %int_0
               OpStore %20 %uint_0 Aligned 2
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-06314"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Memory accesses Aligned operand value 2 is too small, "
                        "the largest scalar type is 4 bytes"));
}

// https://github.com/KhronosGroup/glslang/issues/4024
TEST_F(ValidateMemory, PSBStoreAlignedPointerNot8) {
  const std::string body = R"(
               OpCapability Shader
               OpCapability PhysicalStorageBufferAddresses
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %main "main" %_
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %SSBO Block
               OpMemberDecorate %SSBO 0 Offset 0
               OpDecorate %B Block
               OpMemberDecorate %B 0 Offset 0
               OpDecorate %A Block
               OpMemberDecorate %A 0 Offset 0
               OpDecorate %_ Binding 0
               OpDecorate %_ DescriptorSet 0
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_B PhysicalStorageBuffer
       %SSBO = OpTypeStruct %_ptr_PhysicalStorageBuffer_B
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_A PhysicalStorageBuffer
          %B = OpTypeStruct %_ptr_PhysicalStorageBuffer_A
       %uint = OpTypeInt 32 0
          %A = OpTypeStruct %uint
%_ptr_PhysicalStorageBuffer_A = OpTypePointer PhysicalStorageBuffer %A
%_ptr_PhysicalStorageBuffer_B = OpTypePointer PhysicalStorageBuffer %B
%_ptr_StorageBuffer_SSBO = OpTypePointer StorageBuffer %SSBO
          %_ = OpVariable %_ptr_StorageBuffer_SSBO StorageBuffer
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_B = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_B
%_ptr_PhysicalStorageBuffer__ptr_PhysicalStorageBuffer_A = OpTypePointer PhysicalStorageBuffer %_ptr_PhysicalStorageBuffer_A
     %uint_0 = OpConstant %uint 0
%_ptr_PhysicalStorageBuffer_uint = OpTypePointer PhysicalStorageBuffer %uint
       %main = OpFunction %void None %4
          %6 = OpLabel
         %18 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_B %_ %int_0
         %19 = OpLoad %_ptr_PhysicalStorageBuffer_B %18
         %21 = OpAccessChain %_ptr_PhysicalStorageBuffer__ptr_PhysicalStorageBuffer_A %19 %int_0
         %22 = OpLoad %_ptr_PhysicalStorageBuffer_A %21 Aligned 4
         %25 = OpAccessChain %_ptr_PhysicalStorageBuffer_uint %22 %int_0
               OpStore %25 %uint_0 Aligned 4
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-06314"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Memory accesses Aligned operand value 4 is too small, "
                        "the largest scalar type is 8 bytes"));
}

// https://godbolt.org/z/sbGv6a7os
TEST_F(ValidateMemory, PSBStoreAlignedStructCopyWithDeepDouble) {
  const std::string body = R"(
               OpCapability Shader
               OpCapability Float64
               OpCapability PhysicalStorageBufferAddresses
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %main "main" %_
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %SSBO Block
               OpMemberDecorate %SSBO 0 Offset 0
               OpMemberDecorate %A 0 Offset 0
               OpMemberDecorate %A 1 Offset 8
               OpMemberDecorate %B 0 Offset 0
               OpMemberDecorate %B 1 Offset 16
               OpMemberDecorate %C 0 Offset 0
               OpMemberDecorate %C 1 Offset 32
               OpDecorate %Ptr Block
               OpMemberDecorate %Ptr 0 Offset 0
               OpDecorate %_ Binding 0
               OpDecorate %_ DescriptorSet 0
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_Ptr PhysicalStorageBuffer
       %SSBO = OpTypeStruct %_ptr_PhysicalStorageBuffer_Ptr
      %float = OpTypeFloat 32
    %v3float = OpTypeVector %float 3
       %uint = OpTypeInt 32 0
     %double = OpTypeFloat 64
          %A = OpTypeStruct %uint %double
          %B = OpTypeStruct %v3float %A
          %C = OpTypeStruct %B %uint
        %Ptr = OpTypeStruct %C
%_ptr_PhysicalStorageBuffer_Ptr = OpTypePointer PhysicalStorageBuffer %Ptr
%_ptr_StorageBuffer_SSBO = OpTypePointer StorageBuffer %SSBO
          %_ = OpVariable %_ptr_StorageBuffer_SSBO StorageBuffer
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_Ptr = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_Ptr
        %A_0 = OpTypeStruct %uint %double
        %B_0 = OpTypeStruct %v3float %A_0
        %C_0 = OpTypeStruct %B_0 %uint
%_ptr_Function_C_0 = OpTypePointer Function %C_0
%_ptr_PhysicalStorageBuffer_C = OpTypePointer PhysicalStorageBuffer %C
       %main = OpFunction %void None %4
          %6 = OpLabel
       %newC = OpVariable %_ptr_Function_C_0 Function
         %22 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_Ptr %_ %int_0
         %23 = OpLoad %_ptr_PhysicalStorageBuffer_Ptr %22
         %29 = OpLoad %C_0 %newC
         %31 = OpAccessChain %_ptr_PhysicalStorageBuffer_C %23 %int_0
         %32 = OpCopyLogical %C %29
               OpStore %31 %32 Aligned 4
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-06314"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Memory accesses Aligned operand value 4 is too small, "
                        "the largest scalar type is 8 bytes"));
}

TEST_F(ValidateMemory, PSBStoreAlignedPtrAccessChain) {
  const std::string body = R"(
               OpCapability PhysicalStorageBufferAddresses
               OpCapability Int64
               OpCapability Shader
               OpExtension "SPV_KHR_non_semantic_info"
               OpExtension "SPV_KHR_physical_storage_buffer"
          %2 = OpExtInstImport "NonSemantic.Shader.DebugInfo.100"
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %computeMain "main" %globalParams
               OpExecutionMode %computeMain LocalSize 1 1 1
               OpDecorate %_ptr_PhysicalStorageBuffer_ulong ArrayStride 8
               OpDecorate %GlobalParams_std140 Block
               OpMemberDecorate %GlobalParams_std140 0 Offset 0
               OpDecorate %globalParams Binding 0
               OpDecorate %globalParams DescriptorSet 0
       %void = OpTypeVoid
       %uint = OpTypeInt 32 0
         %12 = OpTypeFunction %void
      %ulong = OpTypeInt 64 0
%_ptr_PhysicalStorageBuffer_ulong = OpTypePointer PhysicalStorageBuffer %ulong
%GlobalParams_std140 = OpTypeStruct %_ptr_PhysicalStorageBuffer_ulong
%_ptr_Uniform_GlobalParams_std140 = OpTypePointer Uniform %GlobalParams_std140
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_ptr_Uniform__ptr_PhysicalStorageBuffer_ulong = OpTypePointer Uniform %_ptr_PhysicalStorageBuffer_ulong
    %ulong_1 = OpConstant %ulong 1
%globalParams = OpVariable %_ptr_Uniform_GlobalParams_std140 Uniform
%computeMain = OpFunction %void None %12
         %13 = OpLabel
         %36 = OpInBoundsAccessChain %_ptr_Uniform__ptr_PhysicalStorageBuffer_ulong %globalParams %int_0
         %37 = OpLoad %_ptr_PhysicalStorageBuffer_ulong %36
         %38 = OpPtrAccessChain %_ptr_PhysicalStorageBuffer_ulong %37 %int_0
               OpStore %38 %ulong_1 Aligned 4
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-06314"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Memory accesses Aligned operand value 4 is too small, "
                        "the largest scalar type is 8 bytes"));
}

TEST_F(ValidateMemory, PSBStoreAlignedUntypedStorageBuffer) {
  const std::string body = R"(
               OpCapability Shader
               OpCapability UntypedPointersKHR
               OpCapability PhysicalStorageBufferAddresses
               OpExtension "SPV_KHR_untyped_pointers"
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %main "main" %_
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %SSBO Block
               OpMemberDecorate %SSBO 0 Offset 0
               OpDecorate %B Block
               OpMemberDecorate %B 0 Offset 0
               OpDecorate %_ Binding 0
               OpDecorate %_ DescriptorSet 0
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_B PhysicalStorageBuffer
       %SSBO = OpTypeStruct %_ptr_PhysicalStorageBuffer_B
       %uint = OpTypeInt 32 0
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
     %uint_0 = OpConstant %uint 0
          %B = OpTypeStruct %uint
%_ptr_PhysicalStorageBuffer_B = OpTypePointer PhysicalStorageBuffer %B
%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_B = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_B
%untyped_ptr = OpTypeUntypedPointerKHR StorageBuffer
          %_ = OpUntypedVariableKHR %untyped_ptr StorageBuffer %SSBO
%_ptr_PhysicalStorageBuffer_uint = OpTypePointer PhysicalStorageBuffer %uint
       %main = OpFunction %void None %4
          %6 = OpLabel
         %16 = OpUntypedAccessChainKHR %untyped_ptr %SSBO %_ %int_0
         %17 = OpLoad %_ptr_PhysicalStorageBuffer_B %16
         %20 = OpAccessChain %_ptr_PhysicalStorageBuffer_uint %17 %int_0
               OpStore %20 %uint_0 Aligned 2
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-06314"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Memory accesses Aligned operand value 2 is too small, "
                        "the largest scalar type is 4 bytes"));
}

TEST_F(ValidateMemory, PSBStoreAlignedUntypedPhysicalStorageBuffer) {
  const std::string body = R"(
               OpCapability Shader
               OpCapability UntypedPointersKHR
               OpCapability PhysicalStorageBufferAddresses
               OpExtension "SPV_KHR_untyped_pointers"
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %main "main" %_
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %SSBO Block
               OpMemberDecorate %SSBO 0 Offset 0
               OpDecorate %B Block
               OpMemberDecorate %B 0 Offset 0
               OpDecorate %_ Binding 0
               OpDecorate %_ DescriptorSet 0
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_B PhysicalStorageBuffer
       %SSBO = OpTypeStruct %_ptr_PhysicalStorageBuffer_B
       %uint = OpTypeInt 32 0
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
     %uint_0 = OpConstant %uint 0
          %B = OpTypeStruct %uint
%_ptr_PhysicalStorageBuffer_B = OpTypePointer PhysicalStorageBuffer %B
%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_B = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_B
%untyped_ptr = OpTypeUntypedPointerKHR StorageBuffer
          %_ = OpUntypedVariableKHR %untyped_ptr StorageBuffer %SSBO
%_ptr_PhysicalStorageBuffer_uint = OpTypePointer PhysicalStorageBuffer %uint
       %main = OpFunction %void None %4
          %6 = OpLabel
         %16 = OpUntypedAccessChainKHR %untyped_ptr %SSBO %_ %int_0
         %17 = OpLoad %_ptr_PhysicalStorageBuffer_B %16
         %20 = OpAccessChain %_ptr_PhysicalStorageBuffer_uint %17 %int_0
               OpStore %20 %uint_0 Aligned 2
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-06314"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Memory accesses Aligned operand value 2 is too small, "
                        "the largest scalar type is 4 bytes"));
}

TEST_F(ValidateMemory, PSBStoreAlignedVariousTypeSuccess) {
  const std::string body = R"(
               OpCapability Shader
               OpCapability Float64
               OpCapability Int8
               OpCapability StorageBuffer8BitAccess
               OpCapability PhysicalStorageBufferAddresses
          %2 = OpExtInstImport "GLSL.std.450"
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %main "main" %_
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %SSBO Block
               OpMemberDecorate %SSBO 0 Offset 0
               OpDecorate %Ptr Block
               OpMemberDecorate %Ptr 0 Offset 0
               OpMemberDecorate %Ptr 1 Offset 4
               OpMemberDecorate %Ptr 2 Offset 8
               OpDecorate %_ Binding 0
               OpDecorate %_ DescriptorSet 0
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
               OpTypeForwardPointer %_ptr_PhysicalStorageBuffer_Ptr PhysicalStorageBuffer
       %SSBO = OpTypeStruct %_ptr_PhysicalStorageBuffer_Ptr
      %uchar = OpTypeInt 8 0
       %uint = OpTypeInt 32 0
     %double = OpTypeFloat 64
        %Ptr = OpTypeStruct %uchar %uint %double
%_ptr_PhysicalStorageBuffer_Ptr = OpTypePointer PhysicalStorageBuffer %Ptr
%_ptr_StorageBuffer_SSBO = OpTypePointer StorageBuffer %SSBO
          %_ = OpVariable %_ptr_StorageBuffer_SSBO StorageBuffer
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_Ptr = OpTypePointer StorageBuffer %_ptr_PhysicalStorageBuffer_Ptr
    %uchar_0 = OpConstant %uchar 0
%_ptr_PhysicalStorageBuffer_uchar = OpTypePointer PhysicalStorageBuffer %uchar
      %int_1 = OpConstant %int 1
     %uint_0 = OpConstant %uint 0
%_ptr_PhysicalStorageBuffer_uint = OpTypePointer PhysicalStorageBuffer %uint
      %int_2 = OpConstant %int 2
   %double_0 = OpConstant %double 0
%_ptr_PhysicalStorageBuffer_double = OpTypePointer PhysicalStorageBuffer %double
       %main = OpFunction %void None %4
          %6 = OpLabel
         %18 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_Ptr %_ %int_0
         %19 = OpLoad %_ptr_PhysicalStorageBuffer_Ptr %18
         %22 = OpAccessChain %_ptr_PhysicalStorageBuffer_uchar %19 %int_0
               OpStore %22 %uchar_0 Aligned 1
         %23 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_Ptr %_ %int_0
         %24 = OpLoad %_ptr_PhysicalStorageBuffer_Ptr %23
         %28 = OpAccessChain %_ptr_PhysicalStorageBuffer_uint %24 %int_1
               OpStore %28 %uint_0 Aligned 4
         %29 = OpAccessChain %_ptr_StorageBuffer__ptr_PhysicalStorageBuffer_Ptr %_ %int_0
         %30 = OpLoad %_ptr_PhysicalStorageBuffer_Ptr %29
         %34 = OpAccessChain %_ptr_PhysicalStorageBuffer_double %30 %int_2
               OpStore %34 %double_0 Aligned 8
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_2);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
}

// https://github.com/KhronosGroup/SPIRV-Tools/issues/6322
TEST_F(ValidateMemory, PSBStoreAlignedUntypedBitcaseSuccess) {
  const std::string body = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VulkanMemoryModel
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Int8
OpCapability PhysicalStorageBufferAddresses
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_vulkan_memory_model"
OpExtension "SPV_KHR_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 Vulkan
OpEntryPoint GLCompute %main "main" %id
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %id BuiltIn GlobalInvocationId
OpDecorate %untyped_phys_ptr ArrayStride 1
OpDecorate %data_buffer Block
OpMemberDecorate %data_buffer 0 Offset 0
OpDecorate %phys_ptrs_struct Block
OpMemberDecorate %phys_ptrs_struct 0 Offset 0
OpMemberDecorate %phys_ptrs_struct 1 Offset 8
OpDecorate %all_data_var DescriptorSet 0
OpDecorate %all_data_var Binding 0

%uint32 = OpTypeInt 32 0
%void = OpTypeVoid
%uint8 = OpTypeInt 8 0
%vec3_uint32 = OpTypeVector %uint32 3
%void_func = OpTypeFunction %void
%c_uint32_0 = OpConstant %uint32 0
%c_uint32_1 = OpConstant %uint32 1
%c_uint32_32 = OpConstant %uint32 32

%uint32_input_ptr = OpTypePointer Input %uint32
%vec3_uint32_input_ptr = OpTypePointer Input %vec3_uint32
%data_buffer = OpTypeStruct %uint8
%untyped_phys_ptr = OpTypeUntypedPointerKHR PhysicalStorageBuffer
%uint8_phys_ptr = OpTypePointer PhysicalStorageBuffer %uint8
%data_buffer_phys_ptr = OpTypePointer PhysicalStorageBuffer %data_buffer
%data_buffer_phys_ptr_ptr = OpTypePointer StorageBuffer %data_buffer_phys_ptr
%phys_ptrs_struct = OpTypeStruct %data_buffer_phys_ptr %data_buffer_phys_ptr
%phys_ptrs_struct_ptr = OpTypePointer StorageBuffer %phys_ptrs_struct
%all_data_var = OpVariable %phys_ptrs_struct_ptr StorageBuffer
%id = OpVariable %vec3_uint32_input_ptr Input
%main = OpFunction %void None %void_func
%label_main = OpLabel
%input_ptr = OpAccessChain %data_buffer_phys_ptr_ptr %all_data_var %c_uint32_0
%input = OpLoad %data_buffer_phys_ptr %input_ptr
%input_loc = OpAccessChain %uint8_phys_ptr %input %c_uint32_0
%output_ptr = OpAccessChain %data_buffer_phys_ptr_ptr %all_data_var %c_uint32_1
%output = OpLoad %data_buffer_phys_ptr %output_ptr
%output_loc = OpUntypedAccessChainKHR %untyped_phys_ptr %data_buffer %output %c_uint32_0
%bitcasted = OpBitcast %untyped_phys_ptr %input_loc
%bitcasted_val = OpLoad %uint8 %bitcasted Aligned 1
OpStore %output_loc %bitcasted_val Aligned 1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

// https://github.com/KhronosGroup/SPIRV-Tools/issues/6322
TEST_F(ValidateMemory, PSBStoreAlignedUntypedBitcase) {
  const std::string body = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VulkanMemoryModel
OpCapability VulkanMemoryModelDeviceScopeKHR
OpCapability Int16
OpCapability PhysicalStorageBufferAddresses
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_vulkan_memory_model"
OpExtension "SPV_KHR_physical_storage_buffer"
OpMemoryModel PhysicalStorageBuffer64 Vulkan
OpEntryPoint GLCompute %main "main" %id
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %id BuiltIn GlobalInvocationId
OpDecorate %untyped_phys_ptr ArrayStride 1
OpDecorate %data_buffer Block
OpMemberDecorate %data_buffer 0 Offset 0
OpDecorate %phys_ptrs_struct Block
OpMemberDecorate %phys_ptrs_struct 0 Offset 0
OpMemberDecorate %phys_ptrs_struct 1 Offset 8
OpDecorate %all_data_var DescriptorSet 0
OpDecorate %all_data_var Binding 0

%uint32 = OpTypeInt 32 0
%void = OpTypeVoid
%uint16 = OpTypeInt 16 0
%vec3_uint32 = OpTypeVector %uint32 3
%void_func = OpTypeFunction %void
%c_uint32_0 = OpConstant %uint32 0
%c_uint32_1 = OpConstant %uint32 1
%c_uint32_32 = OpConstant %uint32 32

%uint32_input_ptr = OpTypePointer Input %uint32
%vec3_uint32_input_ptr = OpTypePointer Input %vec3_uint32
%data_buffer = OpTypeStruct %uint16
%untyped_phys_ptr = OpTypeUntypedPointerKHR PhysicalStorageBuffer
%uint16_phys_ptr = OpTypePointer PhysicalStorageBuffer %uint16
%data_buffer_phys_ptr = OpTypePointer PhysicalStorageBuffer %data_buffer
%data_buffer_phys_ptr_ptr = OpTypePointer StorageBuffer %data_buffer_phys_ptr
%phys_ptrs_struct = OpTypeStruct %data_buffer_phys_ptr %data_buffer_phys_ptr
%phys_ptrs_struct_ptr = OpTypePointer StorageBuffer %phys_ptrs_struct
%all_data_var = OpVariable %phys_ptrs_struct_ptr StorageBuffer
%id = OpVariable %vec3_uint32_input_ptr Input
%main = OpFunction %void None %void_func
%label_main = OpLabel
%input_ptr = OpAccessChain %data_buffer_phys_ptr_ptr %all_data_var %c_uint32_0
%input = OpLoad %data_buffer_phys_ptr %input_ptr
%input_loc = OpAccessChain %uint16_phys_ptr %input %c_uint32_0
%output_ptr = OpAccessChain %data_buffer_phys_ptr_ptr %all_data_var %c_uint32_1
%output = OpLoad %data_buffer_phys_ptr %output_ptr
%output_loc = OpUntypedAccessChainKHR %untyped_phys_ptr %data_buffer %output %c_uint32_0
%bitcasted = OpBitcast %untyped_phys_ptr %input_loc
%bitcasted_val = OpLoad %uint16 %bitcasted Aligned 1
OpStore %output_loc %bitcasted_val Aligned 1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-PhysicalStorageBuffer64-06314"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Memory accesses Aligned operand value 1 is too small, "
                        "the largest scalar type is 2 bytes"));
}

std::string GenCoopMatLoadStoreShader(const std::string& storeMemoryAccess,
                                      const std::string& loadMemoryAccess) {
  std::string s = R"(
OpCapability Shader
OpCapability GroupNonUniform
OpCapability VulkanMemoryModelKHR
OpCapability CooperativeMatrixNV
OpExtension "SPV_KHR_vulkan_memory_model"
OpExtension "SPV_NV_cooperative_matrix"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical VulkanKHR
OpEntryPoint GLCompute %4 "main" %11 %21
OpExecutionMode %4 LocalSize 1 1 1
OpDecorate %11 BuiltIn SubgroupId
OpDecorate %21 BuiltIn WorkgroupId
OpDecorate %74 ArrayStride 4
OpMemberDecorate %75 0 Offset 0
OpDecorate %75 Block
OpDecorate %77 DescriptorSet 0
OpDecorate %77 Binding 0
OpDecorate %92 ArrayStride 4
OpMemberDecorate %93 0 Offset 0
OpDecorate %93 Block
OpDecorate %95 DescriptorSet 0
OpDecorate %95 Binding 1
OpDecorate %102 ArrayStride 4
OpMemberDecorate %103 0 Offset 0
OpDecorate %103 Block
OpDecorate %105 DescriptorSet 0
OpDecorate %105 Binding 2
OpDecorate %117 ArrayStride 4
OpMemberDecorate %118 0 Offset 0
OpDecorate %118 Block
OpDecorate %120 DescriptorSet 0
OpDecorate %120 Binding 3
OpDecorate %123 SpecId 2
OpDecorate %124 SpecId 3
OpDecorate %125 SpecId 4
OpDecorate %126 SpecId 5
OpDecorate %127 SpecId 0
OpDecorate %128 SpecId 1
OpDecorate %129 BuiltIn WorkgroupSize
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 0
%7 = OpTypeVector %6 2
%8 = OpTypePointer Function %7
%10 = OpTypePointer Input %6
%11 = OpVariable %10 Input
%13 = OpConstant %6 2
%19 = OpTypeVector %6 3
%20 = OpTypePointer Input %19
%21 = OpVariable %20 Input
%27 = OpConstantComposite %7 %13 %13
%31 = OpTypePointer Function %6
%33 = OpConstant %6 1024
%34 = OpConstant %6 1
%38 = OpConstant %6 8
%39 = OpConstant %6 0
%68 = OpTypeFloat 32
%69 = OpConstant %6 16
%70 = OpConstant %6 3
%71 = OpTypeCooperativeMatrixNV %68 %70 %69 %38
%72 = OpTypePointer Function %71
%74 = OpTypeRuntimeArray %68
%75 = OpTypeStruct %74
%76 = OpTypePointer StorageBuffer %75
%77 = OpVariable %76 StorageBuffer
%78 = OpTypeInt 32 1
%79 = OpConstant %78 0
%81 = OpConstant %6 5
%82 = OpTypePointer StorageBuffer %68
%84 = OpConstant %6 64
%85 = OpTypeBool
%86 = OpConstantFalse %85
%88 = OpTypePointer Private %71
%89 = OpVariable %88 Private
%92 = OpTypeRuntimeArray %68
%93 = OpTypeStruct %92
%94 = OpTypePointer StorageBuffer %93
%95 = OpVariable %94 StorageBuffer
%99 = OpVariable %88 Private
%102 = OpTypeRuntimeArray %68
%103 = OpTypeStruct %102
%104 = OpTypePointer StorageBuffer %103
%105 = OpVariable %104 StorageBuffer
%109 = OpVariable %88 Private
%111 = OpVariable %88 Private
%112 = OpSpecConstantOp %6 CooperativeMatrixLengthNV %71
%113 = OpSpecConstantOp %78 IAdd %112 %79
%117 = OpTypeRuntimeArray %68
%118 = OpTypeStruct %117
%119 = OpTypePointer StorageBuffer %118
%120 = OpVariable %119 StorageBuffer
%123 = OpSpecConstant %78 1
%124 = OpSpecConstant %78 1
%125 = OpSpecConstant %78 1
%126 = OpSpecConstant %78 1
%127 = OpSpecConstant %6 1
%128 = OpSpecConstant %6 1
%129 = OpSpecConstantComposite %19 %127 %128 %34
%4 = OpFunction %2 None %3
%5 = OpLabel
%9 = OpVariable %8 Function
%18 = OpVariable %8 Function
%32 = OpVariable %31 Function
%44 = OpVariable %31 Function
%52 = OpVariable %31 Function
%60 = OpVariable %31 Function
%73 = OpVariable %72 Function
%91 = OpVariable %72 Function
%101 = OpVariable %72 Function
%12 = OpLoad %6 %11
%14 = OpUMod %6 %12 %13
%15 = OpLoad %6 %11
%16 = OpUDiv %6 %15 %13
%17 = OpCompositeConstruct %7 %14 %16
OpStore %9 %17
%22 = OpLoad %19 %21
%23 = OpVectorShuffle %7 %22 %22 0 1
%24 = OpCompositeExtract %6 %23 0
%25 = OpCompositeExtract %6 %23 1
%26 = OpCompositeConstruct %7 %24 %25
%28 = OpIMul %7 %26 %27
%29 = OpLoad %7 %9
%30 = OpIAdd %7 %28 %29
OpStore %18 %30
%35 = OpAccessChain %31 %18 %34
%36 = OpLoad %6 %35
%37 = OpIMul %6 %33 %36
%40 = OpAccessChain %31 %18 %39
%41 = OpLoad %6 %40
%42 = OpIMul %6 %38 %41
%43 = OpIAdd %6 %37 %42
OpStore %32 %43
%45 = OpAccessChain %31 %18 %34
%46 = OpLoad %6 %45
%47 = OpIMul %6 %33 %46
%48 = OpAccessChain %31 %18 %39
%49 = OpLoad %6 %48
%50 = OpIMul %6 %38 %49
%51 = OpIAdd %6 %47 %50
OpStore %44 %51
%53 = OpAccessChain %31 %18 %34
%54 = OpLoad %6 %53
%55 = OpIMul %6 %33 %54
%56 = OpAccessChain %31 %18 %39
%57 = OpLoad %6 %56
%58 = OpIMul %6 %38 %57
%59 = OpIAdd %6 %55 %58
OpStore %52 %59
%61 = OpAccessChain %31 %18 %34
%62 = OpLoad %6 %61
%63 = OpIMul %6 %33 %62
%64 = OpAccessChain %31 %18 %39
%65 = OpLoad %6 %64
%66 = OpIMul %6 %38 %65
%67 = OpIAdd %6 %63 %66
OpStore %60 %67
%80 = OpLoad %6 %32
%83 = OpAccessChain %82 %77 %79 %80
%87 = OpCooperativeMatrixLoadNV %71 %83 %84 %86 )" +
                  loadMemoryAccess + R"( %81
OpStore %73 %87
%90 = OpLoad %71 %73
OpStore %89 %90
%96 = OpLoad %6 %44
%97 = OpAccessChain %82 %95 %79 %96
%98 = OpCooperativeMatrixLoadNV %71 %97 %84 %86 MakePointerVisibleKHR|NonPrivatePointerKHR %81
OpStore %91 %98
%100 = OpLoad %71 %91
OpStore %99 %100
%106 = OpLoad %6 %52
%107 = OpAccessChain %82 %105 %79 %106
%108 = OpCooperativeMatrixLoadNV %71 %107 %84 %86 MakePointerVisibleKHR|NonPrivatePointerKHR %81
OpStore %101 %108
%110 = OpLoad %71 %101
OpStore %109 %110
%114 = OpConvertSToF %68 %113
%115 = OpCompositeConstruct %71 %114
OpStore %111 %115
%116 = OpLoad %71 %111
%121 = OpLoad %6 %60
%122 = OpAccessChain %82 %120 %79 %121
OpCooperativeMatrixStoreNV %122 %116 %84 %86 )" +
                  storeMemoryAccess + R"( %81
OpReturn
OpFunctionEnd
)";

  return s;
}

TEST_F(ValidateMemory, CoopMatLoadStoreSuccess) {
  std::string spirv =
      GenCoopMatLoadStoreShader("MakePointerAvailableKHR|NonPrivatePointerKHR",
                                "MakePointerVisibleKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, CoopMatStoreMemoryAccessFail) {
  std::string spirv =
      GenCoopMatLoadStoreShader("MakePointerVisibleKHR|NonPrivatePointerKHR",
                                "MakePointerVisibleKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MakePointerVisibleKHR cannot be used with OpStore"));
}

TEST_F(ValidateMemory, CoopMatLoadMemoryAccessFail) {
  std::string spirv =
      GenCoopMatLoadStoreShader("MakePointerAvailableKHR|NonPrivatePointerKHR",
                                "MakePointerAvailableKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MakePointerAvailableKHR cannot be used with OpLoad"));
}

TEST_F(ValidateMemory, CoopMatInvalidStorageClassFail) {
  const std::string body =
      R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeMatrixNV
OpExtension "SPV_NV_cooperative_matrix"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0

%u32_8 = OpConstant %u32 8
%subgroup = OpConstant %u32 3

%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8

%str = OpTypeStruct %f16mat
%str_ptr = OpTypePointer Workgroup %str
%sh = OpVariable %str_ptr Workgroup

%main = OpFunction %void None %func
%main_entry = OpLabel

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str());
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Cooperative matrix types (or types containing them) can only be "
          "allocated in Function or Private storage classes or as function "
          "parameters"));
}

TEST_F(ValidateMemory, CoopMatMatrixLengthResultTypeBad) {
  const std::string body =
      R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeMatrixNV
OpExtension "SPV_NV_cooperative_matrix"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0
%i32 = OpTypeInt 32 1

%u32_8 = OpConstant %u32 8
%subgroup = OpConstant %u32 3

%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8

%main = OpFunction %void None %func
%main_entry = OpLabel

%1 = OpCooperativeMatrixLengthNV %i32 %f16mat

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str());
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The Result Type of OpCooperativeMatrixLengthNV <id> "
                "'11[%11]' must be OpTypeInt with width 32 and signedness 0"));
}

TEST_F(ValidateMemory, CoopMatMatrixLengthOperandTypeBad) {
  const std::string body =
      R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeMatrixNV
OpExtension "SPV_NV_cooperative_matrix"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0
%i32 = OpTypeInt 32 1

%u32_8 = OpConstant %u32 8
%subgroup = OpConstant %u32 3

%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8

%main = OpFunction %void None %func
%main_entry = OpLabel

%1 = OpCooperativeMatrixLengthNV %u32 %u32

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str());
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The type in OpCooperativeMatrixLengthNV <id> '5[%uint]' "
                "must be OpTypeCooperativeMatrixNV"));
}

TEST_F(ValidateMemory, CoopMatMatrixLengthGood) {
  const std::string body =
      R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeMatrixNV
OpExtension "SPV_NV_cooperative_matrix"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0
%i32 = OpTypeInt 32 1

%u32_8 = OpConstant %u32 8
%subgroup = OpConstant %u32 3

%f16mat = OpTypeCooperativeMatrixNV %f16 %subgroup %u32_8 %u32_8

%main = OpFunction %void None %func
%main_entry = OpLabel

%1 = OpCooperativeMatrixLengthNV %u32 %f16mat

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str());
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

std::string GenCoopMatLoadStoreShaderKHR(
    const std::string& storeMemoryAccess, const std::string& loadMemoryAccess,
    unsigned layout = 0, bool useSpecConstantLayout = false,
    bool useStoreStride = true, bool useLoadStride = true,
    bool useConstantStride = true, unsigned stride = 4, unsigned rows = 16,
    unsigned cols = 16) {
  std::string s = R"(
OpCapability Shader
OpCapability GroupNonUniform
OpCapability VulkanMemoryModelKHR
OpCapability CooperativeMatrixKHR
OpCapability CooperativeMatrixLayoutsARM
OpExtension "SPV_KHR_vulkan_memory_model"
OpExtension "SPV_KHR_cooperative_matrix"
OpExtension "SPV_ARM_cooperative_matrix_layouts"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical VulkanKHR
OpEntryPoint GLCompute %4 "main" %11 %21
OpExecutionMode %4 LocalSize 1 1 1
OpDecorate %11 BuiltIn SubgroupId
OpDecorate %21 BuiltIn WorkgroupId
OpDecorate %74 ArrayStride 4
OpMemberDecorate %75 0 Offset 0
OpDecorate %75 Block
OpDecorate %77 DescriptorSet 0
OpDecorate %77 Binding 0
OpDecorate %92 ArrayStride 4
OpMemberDecorate %93 0 Offset 0
OpDecorate %93 Block
OpDecorate %95 DescriptorSet 0
OpDecorate %95 Binding 1
OpDecorate %102 ArrayStride 4
OpMemberDecorate %103 0 Offset 0
OpDecorate %103 Block
OpDecorate %105 DescriptorSet 0
OpDecorate %105 Binding 2
OpDecorate %117 ArrayStride 4
OpMemberDecorate %118 0 Offset 0
OpDecorate %118 Block
OpDecorate %120 DescriptorSet 0
OpDecorate %120 Binding 3
OpDecorate %123 SpecId 2
OpDecorate %124 SpecId 3
OpDecorate %125 SpecId 4
OpDecorate %126 SpecId 5
OpDecorate %127 SpecId 0
OpDecorate %128 SpecId 1
OpDecorate %129 BuiltIn WorkgroupSize
%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 0
%7 = OpTypeVector %6 2
%8 = OpTypePointer Function %7
%10 = OpTypePointer Input %6
%ptr_uint_Function = OpTypePointer Function %6
%11 = OpVariable %10 Input
%13 = OpConstant %6 2
%19 = OpTypeVector %6 3
%20 = OpTypePointer Input %19
%21 = OpVariable %20 Input
%27 = OpConstantComposite %7 %13 %13
%31 = OpTypePointer Function %6
%33 = OpConstant %6 1024
%34 = OpConstant %6 1
%cols = OpConstant %6 )" +
                  std::to_string(cols) + R"(
%uint_0 = OpConstant %6 0
)";
  if (useSpecConstantLayout) {
    s += "%layout = OpSpecConstant %6 " + std::to_string(layout);
  } else {
    s += "%layout = OpConstant %6 " + std::to_string(layout);
  }
  s += R"(
%68 = OpTypeFloat 32
%rows = OpConstant %6 )" +
       std::to_string(rows) + R"(
%scope = OpConstant %6 3
%71 = OpTypeCooperativeMatrixKHR %68 %scope %rows %cols %uint_0
%72 = OpTypePointer Function %71
%74 = OpTypeRuntimeArray %68
%75 = OpTypeStruct %74
%76 = OpTypePointer StorageBuffer %75
%77 = OpVariable %76 StorageBuffer
%78 = OpTypeInt 32 1
%79 = OpConstant %78 0
%81 = OpConstant %6 5
%82 = OpTypePointer StorageBuffer %68
)";
  if (useConstantStride) {
    s += "%stride = OpConstant %6 " + std::to_string(stride);
  }
  s += R"(
%88 = OpTypePointer Private %71
%89 = OpVariable %88 Private
%92 = OpTypeRuntimeArray %68
%93 = OpTypeStruct %92
%94 = OpTypePointer StorageBuffer %93
%95 = OpVariable %94 StorageBuffer
%99 = OpVariable %88 Private
%102 = OpTypeRuntimeArray %68
%103 = OpTypeStruct %102
%104 = OpTypePointer StorageBuffer %103
%105 = OpVariable %104 StorageBuffer
%109 = OpVariable %88 Private
%111 = OpVariable %88 Private
%112 = OpSpecConstantOp %6 CooperativeMatrixLengthKHR %71
%113 = OpSpecConstantOp %78 IAdd %112 %79
%117 = OpTypeRuntimeArray %68
%118 = OpTypeStruct %117
%119 = OpTypePointer StorageBuffer %118
%120 = OpVariable %119 StorageBuffer
%123 = OpSpecConstant %78 1
%124 = OpSpecConstant %78 1
%125 = OpSpecConstant %78 1
%126 = OpSpecConstant %78 1
%127 = OpSpecConstant %6 1
%128 = OpSpecConstant %6 1
%129 = OpSpecConstantComposite %19 %127 %128 %34
%4 = OpFunction %2 None %3
%5 = OpLabel
%9 = OpVariable %8 Function
%18 = OpVariable %8 Function
%stride_var = OpVariable %ptr_uint_Function Function
%32 = OpVariable %31 Function
%44 = OpVariable %31 Function
%52 = OpVariable %31 Function
%60 = OpVariable %31 Function
%73 = OpVariable %72 Function
%91 = OpVariable %72 Function
%101 = OpVariable %72 Function
)";
  if (!useConstantStride) {
    s += "%stride = OpLoad %6 %stride_var";
  }
  s += R"(
%12 = OpLoad %6 %11
%14 = OpUMod %6 %12 %13
%15 = OpLoad %6 %11
%16 = OpUDiv %6 %15 %13
%17 = OpCompositeConstruct %7 %14 %16
OpStore %9 %17
%22 = OpLoad %19 %21
%23 = OpVectorShuffle %7 %22 %22 0 1
%24 = OpCompositeExtract %6 %23 0
%25 = OpCompositeExtract %6 %23 1
%26 = OpCompositeConstruct %7 %24 %25
%28 = OpIMul %7 %26 %27
%29 = OpLoad %7 %9
%30 = OpIAdd %7 %28 %29
OpStore %18 %30
%35 = OpAccessChain %31 %18 %34
%36 = OpLoad %6 %35
%37 = OpIMul %6 %33 %36
%40 = OpAccessChain %31 %18 %uint_0
%41 = OpLoad %6 %40
%42 = OpIMul %6 %cols %41
%43 = OpIAdd %6 %37 %42
OpStore %32 %43
%45 = OpAccessChain %31 %18 %34
%46 = OpLoad %6 %45
%47 = OpIMul %6 %33 %46
%48 = OpAccessChain %31 %18 %uint_0
%49 = OpLoad %6 %48
%50 = OpIMul %6 %cols %49
%51 = OpIAdd %6 %47 %50
OpStore %44 %51
%53 = OpAccessChain %31 %18 %34
%54 = OpLoad %6 %53
%55 = OpIMul %6 %33 %54
%56 = OpAccessChain %31 %18 %uint_0
%57 = OpLoad %6 %56
%58 = OpIMul %6 %cols %57
%59 = OpIAdd %6 %55 %58
OpStore %52 %59
%61 = OpAccessChain %31 %18 %34
%62 = OpLoad %6 %61
%63 = OpIMul %6 %33 %62
%64 = OpAccessChain %31 %18 %uint_0
%65 = OpLoad %6 %64
%66 = OpIMul %6 %cols %65
%67 = OpIAdd %6 %63 %66
OpStore %60 %67
%80 = OpLoad %6 %32
%83 = OpAccessChain %82 %77 %79 %80
)";
  if (useLoadStride) {
    s += "%87 = OpCooperativeMatrixLoadKHR %71 %83 %layout %stride " +
         loadMemoryAccess + " %81";
  } else {
    s += "%87 = OpCooperativeMatrixLoadKHR %71 %83 %layout";
  }
  s += R"(
OpStore %73 %87
%90 = OpLoad %71 %73
OpStore %89 %90
%96 = OpLoad %6 %44
%97 = OpAccessChain %82 %95 %79 %96
%98 = OpCooperativeMatrixLoadKHR %71 %97 %layout %stride MakePointerVisibleKHR|NonPrivatePointerKHR %81
OpStore %91 %98
%100 = OpLoad %71 %91
OpStore %99 %100
%106 = OpLoad %6 %52
%107 = OpAccessChain %82 %105 %79 %106
%108 = OpCooperativeMatrixLoadKHR %71 %107 %layout %stride MakePointerVisibleKHR|NonPrivatePointerKHR %81
OpStore %101 %108
%110 = OpLoad %71 %101
OpStore %109 %110
%114 = OpConvertSToF %68 %113
%115 = OpCompositeConstruct %71 %114
OpStore %111 %115
%116 = OpLoad %71 %111
%121 = OpLoad %6 %60
%122 = OpAccessChain %82 %120 %79 %121
)";
  if (useStoreStride) {
    s += "OpCooperativeMatrixStoreKHR %122 %116 %layout %stride " +
         storeMemoryAccess + " %81";
  } else {
    s += "OpCooperativeMatrixStoreKHR %122 %116 %layout";
  }
  s += R"(
OpReturn
OpFunctionEnd
)";

  return s;
}

TEST_F(ValidateMemory, CoopMatKHRLoadStoreSuccess) {
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      "MakePointerVisibleKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

struct StrideMissingCase {
  unsigned layout;
  bool useLoadStride;
  bool useStoreStride;
};

using ValidateCoopMatrixStrideMissing =
    spvtest::ValidateBase<StrideMissingCase>;

INSTANTIATE_TEST_SUITE_P(
    CoopMatrixStrideMissing, ValidateCoopMatrixStrideMissing,
    Values(
        StrideMissingCase{(unsigned)spv::CooperativeMatrixLayout::RowMajorKHR,
                          false, true},
        StrideMissingCase{(unsigned)spv::CooperativeMatrixLayout::RowMajorKHR,
                          true, false},
        StrideMissingCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnMajorKHR, false,
            true},
        StrideMissingCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnMajorKHR, true,
            false},
        StrideMissingCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM,
            false, true},
        StrideMissingCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM,
            true, false},
        StrideMissingCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            false, true},
        StrideMissingCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            true, false}));

TEST_P(ValidateCoopMatrixStrideMissing, CoopMatKHRLoadStrideMissingFail) {
  const StrideMissingCase& param = GetParam();
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      "MakePointerVisibleKHR|NonPrivatePointerKHR", param.layout,
      false /*useSpecConstantLayout*/, param.useStoreStride,
      param.useLoadStride);
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MemoryLayout " + std::to_string(param.layout) +
                        " requires a Stride"));
}

TEST_F(ValidateMemory, CoopMatKHRMemoryLayoutFromSpecConstantSuccess) {
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      "MakePointerVisibleKHR|NonPrivatePointerKHR",
      (unsigned)spv::CooperativeMatrixLayout::RowMajorKHR,
      true /*useSpecConstantLayout*/);

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, CoopMatKHRStoreMemoryAccessFail) {
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerVisibleKHR|NonPrivatePointerKHR",
      "MakePointerVisibleKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MakePointerVisibleKHR cannot be used with OpStore"));
}

TEST_F(ValidateMemory, CoopMatKHRLoadMemoryAccessFail) {
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      "MakePointerAvailableKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MakePointerAvailableKHR cannot be used with OpLoad"));
}

TEST_F(ValidateMemory,
       CoopMatLoadArmRowBlockedInterleavedLayoutNonConstantStrideFail) {
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM,
      false, /* useSpecConstantLayout */
      true,  /* useStoreStride */
      true,  /* useLoadStride */
      false /* useConstantStride */);
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MemoryLayout 4202 requires Stride come from a "
                        "constant instruction"));
}

TEST_F(ValidateMemory,
       CoopMatLoadArmColumnBlockedInterleavedLayoutNonConstantStrideFail) {
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
      false, /* useSpecConstantLayout */
      true,  /* useStoreStride */
      true,  /* useLoadStride */
      false /* useConstantStride */);
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MemoryLayout 4203 requires Stride come from a "
                        "constant instruction"));
}

struct StrideValueCase {
  unsigned layout;
  unsigned stride;
  bool valid;
};

using ValidateCoopMatrixStrideValue = spvtest::ValidateBase<StrideValueCase>;

INSTANTIATE_TEST_SUITE_P(
    CoopMatrixStrideValue, ValidateCoopMatrixStrideValue,
    Values(
        StrideValueCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 1,
            true},
        StrideValueCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 2,
            true},
        StrideValueCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 4,
            true},
        StrideValueCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 8,
            false},
        StrideValueCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            1, true},
        StrideValueCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            2, true},
        StrideValueCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            4, true},
        StrideValueCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            8, false}));

TEST_P(ValidateCoopMatrixStrideValue, CoopMatLoadStoreStrideValue) {
  const StrideValueCase& param = GetParam();
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      "MakePointerVisibleKHR|NonPrivatePointerKHR", param.layout,
      false /*useSpecConstantLayout*/, true /*useStoreStride*/,
      true /*useLoadStride*/, true /*useConstantStride*/, param.stride);
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  if (param.valid) {
    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  } else {
    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("MemoryLayout " + std::to_string(param.layout) +
                          " requires Stride be 1, 2, or 4"));
  }
}

struct MatrixSizeCase {
  unsigned layout;
  unsigned rows;
  unsigned cols;
  unsigned stride;
  bool valid_rows;
  bool valid_cols;
};

using ValidateCoopMatrixSize = spvtest::ValidateBase<MatrixSizeCase>;

INSTANTIATE_TEST_SUITE_P(
    CoopMatrixMatrixSize, ValidateCoopMatrixSize,
    Values(
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 4,
            4, 1, true, true},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 4,
            8, 2, true, true},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 2,
            4, 1, false, true},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 2,
            4, 2, false, false},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 4,
            2, 1, true, false},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM, 4,
            4, 2, true, false},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            4, 4, 1, true, true},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            8, 4, 2, true, true},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            4, 2, 1, true, false},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            4, 2, 2, false, false},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            2, 4, 1, false, true},
        MatrixSizeCase{
            (unsigned)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM,
            4, 4, 2, false, true}));

TEST_P(ValidateCoopMatrixSize, CoopMatLoadStoreMatrixSize) {
  const MatrixSizeCase& param = GetParam();
  std::string spirv = GenCoopMatLoadStoreShaderKHR(
      "MakePointerAvailableKHR|NonPrivatePointerKHR",
      "MakePointerVisibleKHR|NonPrivatePointerKHR", param.layout,
      false /*useSpecConstantLayout*/, true /*useStoreStride*/,
      true /*useLoadStride*/, true /*useConstantStride*/, param.stride,
      param.rows, param.cols);
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);

  uint32_t rows_required_multiple = 4;
  uint32_t cols_required_multiple =
      16 / 4;  // The shader always uses FP32 matrix components

  if (param.layout ==
      (uint64_t)spv::CooperativeMatrixLayout::RowBlockedInterleavedARM) {
    cols_required_multiple *= param.stride;
  }
  if (param.layout ==
      (uint64_t)spv::CooperativeMatrixLayout::ColumnBlockedInterleavedARM) {
    rows_required_multiple *= param.stride;
  }

  if (param.valid_rows && param.valid_cols) {
    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  } else if (param.valid_rows) {
    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
    EXPECT_THAT(
        getDiagnosticString(),
        HasSubstr("MemoryLayout " + std::to_string(param.layout) +
                  " with a Stride of " + std::to_string(param.stride) +
                  " requires that the number of columns be a multiple of " +
                  std::to_string(cols_required_multiple)));
  } else {
    EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
    EXPECT_THAT(
        getDiagnosticString(),
        HasSubstr("MemoryLayout " + std::to_string(param.layout) +
                  " with a Stride of " + std::to_string(param.stride) +
                  " requires that the number of rows be a multiple of " +
                  std::to_string(rows_required_multiple)));
  }
}

TEST_F(ValidateMemory, CoopMatKHRInvalidStorageClassFail) {
  const std::string body = R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeMatrixKHR
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_cooperative_matrix"
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0

%u32_8 = OpConstant %u32 8
%use_A = OpConstant %u32 0
%subgroup = OpConstant %u32 3

%f16mat = OpTypeCooperativeMatrixKHR %f16 %subgroup %u32_8 %u32_8 %use_A

%str = OpTypeStruct %f16mat
%str_ptr = OpTypePointer Workgroup %str
%sh = OpVariable %str_ptr Workgroup

%main = OpFunction %void None %func
%main_entry = OpLabel

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Cooperative matrix types (or types containing them) can only be "
          "allocated in Function or Private storage classes or as function "
          "parameters"));
}

TEST_F(ValidateMemory, CoopMatMatrixKHRLengthResultTypeBad) {
  const std::string body = R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeMatrixKHR
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_cooperative_matrix"
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0
%i32 = OpTypeInt 32 1

%u32_8 = OpConstant %u32 8
%use_A = OpConstant %u32 0
%subgroup = OpConstant %u32 3

%f16mat = OpTypeCooperativeMatrixKHR %f16 %subgroup %u32_8 %u32_8 %use_A

%main = OpFunction %void None %func
%main_entry = OpLabel

%1 = OpCooperativeMatrixLengthKHR %i32 %f16mat

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The Result Type of OpCooperativeMatrixLengthKHR <id> "
                "'12[%12]' must be OpTypeInt with width 32 and signedness 0"));
}

TEST_F(ValidateMemory, CoopMatMatrixKHRLengthOperandTypeBad) {
  const std::string body =
      R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeMatrixKHR
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_cooperative_matrix"
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0
%i32 = OpTypeInt 32 1

%u32_8 = OpConstant %u32 8
%use_A = OpConstant %u32 0
%subgroup = OpConstant %u32 3

%f16mat = OpTypeCooperativeMatrixKHR %f16 %subgroup %u32_8 %u32_8 %use_A

%main = OpFunction %void None %func
%main_entry = OpLabel

%1 = OpCooperativeMatrixLengthKHR %u32 %u32

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The type in OpCooperativeMatrixLengthKHR <id> '5[%uint]' "
                "must be OpTypeCooperativeMatrixKHR"));
}

TEST_F(ValidateMemory, CoopMatMatrixKHRLengthGood) {
  const std::string body =
      R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeMatrixKHR
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_cooperative_matrix"
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0
%i32 = OpTypeInt 32 1

%u32_8 = OpConstant %u32 8
%use_A = OpConstant %u32 0
%subgroup = OpConstant %u32 3

%f16mat = OpTypeCooperativeMatrixKHR %f16 %subgroup %u32_8 %u32_8 %use_A

%main = OpFunction %void None %func
%main_entry = OpLabel

%1 = OpCooperativeMatrixLengthKHR %u32 %f16mat

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str(), SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanRTAOutsideOfStructBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%sampler_t = OpTypeSampler
%array_t = OpTypeRuntimeArray %sampler_t
%array_ptr = OpTypePointer UniformConstant %array_t
%2 = OpVariable %array_ptr UniformConstant
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpVariable, <id> '5[%5]', is attempting to create memory for an "
          "illegal type, OpTypeRuntimeArray.\nFor Vulkan OpTypeRuntimeArray "
          "can only appear as the final member of an OpTypeStruct, thus cannot "
          "be instantiated via OpVariable, unless the RuntimeDescriptorArray "
          "Capability is declared\n  %5 = OpVariable "
          "%_ptr_UniformConstant__runtimearr_2 UniformConstant\n"));
}

TEST_F(ValidateMemory, VulkanRTAOutsideOfStructWithRuntimeDescriptorArrayGood) {
  std::string spirv = R"(
OpCapability Shader
OpCapability RuntimeDescriptorArrayEXT
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
OpDecorate %3 DescriptorSet 0
OpDecorate %3 Binding 1
%sampler_t = OpTypeSampler
%uint = OpTypeInt 32 0
%array_t = OpTypeRuntimeArray %sampler_t
%struct = OpTypeStruct %uint
%sb_array_t = OpTypeRuntimeArray %struct
%array_sb_ptr = OpTypePointer StorageBuffer %sb_array_t
%2 = OpVariable %array_sb_ptr StorageBuffer
%array_uc_ptr = OpTypePointer UniformConstant %array_t
%3 = OpVariable %array_uc_ptr UniformConstant
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(
    ValidateMemory,
    VulkanRTAOutsideOfStructWithRuntimeDescriptorArrayAndWrongStorageClassBad) {
  std::string spirv = R"(
OpCapability Shader
OpCapability RuntimeDescriptorArrayEXT
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%uint_t = OpTypeInt 32 0
%array_t = OpTypeRuntimeArray %uint_t
%array_ptr = OpTypePointer Workgroup %array_t
%2 = OpVariable %array_ptr Workgroup
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("For Vulkan with RuntimeDescriptorArray, a variable "
                "containing OpTypeRuntimeArray must have storage class of "
                "StorageBuffer, Uniform, or UniformConstant.\n  %5 = "
                "OpVariable %_ptr_Workgroup__runtimearr_uint Workgroup\n"));
}

TEST_F(ValidateMemory, VulkanRTAInsideStorageBufferStructGood) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %array_t ArrayStride 4
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t Block
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
%uint_t = OpTypeInt 32 0
%array_t = OpTypeRuntimeArray %uint_t
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer StorageBuffer %struct_t
%2 = OpVariable %struct_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanRTAInsideWrongStorageClassStructBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %struct_t Block
%uint_t = OpTypeInt 32 0
%array_t = OpTypeRuntimeArray %uint_t
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer Workgroup %struct_t
%2 = OpVariable %struct_ptr Workgroup
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "For Vulkan, OpTypeStruct variables containing OpTypeRuntimeArray "
          "must have storage class of StorageBuffer, PhysicalStorageBuffer, or "
          "Uniform.\n  %6 = "
          "OpVariable %_ptr_Workgroup__struct_2 Workgroup\n"));
}

TEST_F(ValidateMemory, VulkanRTAInsideStorageBufferStructWithoutBlockBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %struct_t BufferBlock
%uint_t = OpTypeInt 32 0
%array_t = OpTypeRuntimeArray %uint_t
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer StorageBuffer %struct_t
%2 = OpVariable %struct_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("For Vulkan, an OpTypeStruct variable containing an "
                        "OpTypeRuntimeArray must be decorated with Block if it "
                        "has storage class StorageBuffer or "
                        "PhysicalStorageBuffer.\n  %6 = OpVariable "
                        "%_ptr_StorageBuffer__struct_2 StorageBuffer\n"));
}

TEST_F(ValidateMemory, VulkanRTAInsideUniformStructGood) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %array_t ArrayStride 4
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t BufferBlock
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
%uint_t = OpTypeInt 32 0
%array_t = OpTypeRuntimeArray %uint_t
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer Uniform %struct_t
%2 = OpVariable %struct_ptr Uniform
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanRTAInsideUniformStructWithoutBufferBlock) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %array_t ArrayStride 16
OpDecorate %struct_t Block
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %2 Binding 0
OpDecorate %2 DescriptorSet 0
%uint_t = OpTypeInt 32 0
%array_t = OpTypeRuntimeArray %uint_t
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer Uniform %struct_t
%2 = OpVariable %struct_ptr Uniform
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanArrayLengthUniformRuntimeArray) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %array_t ArrayStride 16
OpDecorate %struct_t Block
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %2 Binding 0
OpDecorate %2 DescriptorSet 0
%uint_t = OpTypeInt 32 0
%array_t = OpTypeRuntimeArray %uint_t
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer Uniform %struct_t
%2 = OpVariable %struct_ptr Uniform
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
%length = OpArrayLength %uint_t %2 0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpArrayLength-11805"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpArrayLength must not be used on the "
                        "OpTypeRuntimeArray inside a Uniform block"));
}

TEST_F(ValidateMemory, VulkanRTAInsideRTABad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%sampler_t = OpTypeSampler
%inner_array_t = OpTypeRuntimeArray %sampler_t
%array_t = OpTypeRuntimeArray %inner_array_t
%array_ptr = OpTypePointer UniformConstant %array_t
%2 = OpVariable %array_ptr UniformConstant
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpTypeRuntimeArray Element Type <id> '3[%_runtimearr_2]' is not "
          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_2 = "
          "OpTypeRuntimeArray %_runtimearr_2\n"));
}

TEST_F(ValidateMemory, VulkanRTAInsideRTAWithRuntimeDescriptorArrayBad) {
  std::string spirv = R"(
OpCapability RuntimeDescriptorArrayEXT
OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %struct Block
%uint_t = OpTypeInt 32 0
%inner_array_t = OpTypeRuntimeArray %uint_t
%array_t = OpTypeRuntimeArray %inner_array_t
%struct = OpTypeStruct %array_t
%array_ptr = OpTypePointer StorageBuffer %struct
%2 = OpVariable %array_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpTypeRuntimeArray Element Type <id> '4[%_runtimearr_uint]' is not "
          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
          "OpTypeRuntimeArray %_runtimearr_uint\n"));
}

TEST_F(ValidateMemory,
       VulkanUniformStructInsideRTAWithRuntimeDescriptorArrayGood) {
  std::string spirv = R"(
OpCapability RuntimeDescriptorArrayEXT
OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t Block
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
%uint_t = OpTypeInt 32 0
%struct_t = OpTypeStruct %uint_t
%array_t = OpTypeRuntimeArray %struct_t
%array_ptr = OpTypePointer Uniform %array_t
%2 = OpVariable %array_ptr Uniform
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanRTAInsideRTAInsideStructBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %array_t ArrayStride 4
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t Block
%uint_t = OpTypeInt 32 0
%inner_array_t = OpTypeRuntimeArray %uint_t
%array_t = OpTypeRuntimeArray %inner_array_t
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer StorageBuffer %struct_t
%2 = OpVariable %struct_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpTypeRuntimeArray Element Type <id> '5[%_runtimearr_uint]' is not "
          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
          "OpTypeRuntimeArray %_runtimearr_uint\n"));
}

TEST_F(ValidateMemory,
       VulkanRTAInsideRTAInsideStructWithRuntimeDescriptorArrayBad) {
  std::string spirv = R"(
OpCapability RuntimeDescriptorArrayEXT
OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %array_t ArrayStride 4
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t Block
%uint_t = OpTypeInt 32 0
%inner_array_t = OpTypeRuntimeArray %uint_t
%array_t = OpTypeRuntimeArray %inner_array_t
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer StorageBuffer %struct_t
%2 = OpVariable %struct_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpTypeRuntimeArray Element Type <id> '5[%_runtimearr_uint]' is not "
          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
          "OpTypeRuntimeArray %_runtimearr_uint\n"));
}

TEST_F(ValidateMemory, VulkanRTAInsideArrayBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%uint_t = OpTypeInt 32 0
%dim = OpConstant %uint_t 1
%sampler_t = OpTypeSampler
%inner_array_t = OpTypeRuntimeArray %sampler_t
%array_t = OpTypeArray %inner_array_t %dim
%array_ptr = OpTypePointer UniformConstant %array_t
%2 = OpVariable %array_ptr UniformConstant
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("OpTypeArray Element Type <id> '5[%_runtimearr_4]' is not "
                "valid in Vulkan environments.\n  %_arr__runtimearr_4_uint_1 = "
                "OpTypeArray %_runtimearr_4 %uint_1\n"));
}

TEST_F(ValidateMemory, VulkanRTAInsideArrayWithRuntimeDescriptorArrayBad) {
  std::string spirv = R"(
OpCapability RuntimeDescriptorArrayEXT
OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %struct Block
%uint_t = OpTypeInt 32 0
%dim = OpConstant %uint_t 1
%sampler_t = OpTypeSampler
%inner_array_t = OpTypeRuntimeArray %uint_t
%array_t = OpTypeRuntimeArray %inner_array_t
%struct = OpTypeStruct %array_t
%array_ptr = OpTypePointer StorageBuffer %struct
%2 = OpVariable %array_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpTypeRuntimeArray Element Type <id> '6[%_runtimearr_uint]' is not "
          "valid in Vulkan environments.\n  %_runtimearr__runtimearr_uint = "
          "OpTypeRuntimeArray %_runtimearr_uint\n"));
}

TEST_F(ValidateMemory, VulkanRTAInsideArrayInsideStructBad) {
  std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %array_t ArrayStride 4
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t Block
%uint_t = OpTypeInt 32 0
%dim = OpConstant %uint_t 1
%inner_array_t = OpTypeRuntimeArray %uint_t
%array_t = OpTypeArray %inner_array_t %dim
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer StorageBuffer %struct_t
%2 = OpVariable %struct_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpTypeArray Element Type <id> '6[%_runtimearr_uint]' is not "
          "valid in Vulkan environments.\n  %_arr__runtimearr_uint_uint_1 "
          "= OpTypeArray %_runtimearr_uint %uint_1\n"));
}

TEST_F(ValidateMemory,
       VulkanRTAInsideArrayInsideStructWithRuntimeDescriptorArrayBad) {
  std::string spirv = R"(
OpCapability RuntimeDescriptorArrayEXT
OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %array_t ArrayStride 4
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t Block
%uint_t = OpTypeInt 32 0
%dim = OpConstant %uint_t 1
%inner_array_t = OpTypeRuntimeArray %uint_t
%array_t = OpTypeArray %inner_array_t %dim
%struct_t = OpTypeStruct %array_t
%struct_ptr = OpTypePointer StorageBuffer %struct_t
%2 = OpVariable %struct_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeRuntimeArray-04680"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "OpTypeArray Element Type <id> '6[%_runtimearr_uint]' is not "
          "valid in Vulkan environments.\n  %_arr__runtimearr_uint_uint_1 "
          "= OpTypeArray %_runtimearr_uint %uint_1\n"));
}

TEST_F(ValidateMemory, VulkanRTAStructInsideRTAWithRuntimeDescriptorArrayGood) {
  std::string spirv = R"(
OpCapability RuntimeDescriptorArrayEXT
OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %inner_array_t ArrayStride 4
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t Block
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
%uint_t = OpTypeInt 32 0
%inner_array_t = OpTypeRuntimeArray %uint_t
%struct_t = OpTypeStruct %inner_array_t
%array_t = OpTypeRuntimeArray %struct_t
%array_ptr = OpTypePointer StorageBuffer %array_t
%2 = OpVariable %array_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, VulkanRTAStructInsideArrayGood) {
  std::string spirv = R"(
OpCapability RuntimeDescriptorArrayEXT
OpCapability Shader
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
OpDecorate %inner_array_t ArrayStride 4
OpMemberDecorate %struct_t 0 Offset 0
OpDecorate %struct_t Block
OpDecorate %2 DescriptorSet 0
OpDecorate %2 Binding 0
%uint_t = OpTypeInt 32 0
%inner_array_t = OpTypeRuntimeArray %uint_t
%struct_t = OpTypeStruct %inner_array_t
%array_size = OpConstant %uint_t 5
%array_t = OpTypeArray %struct_t %array_size
%array_ptr = OpTypePointer StorageBuffer %array_t
%2 = OpVariable %array_ptr StorageBuffer
%void = OpTypeVoid
%func_t = OpTypeFunction %void
%func = OpFunction %void None %func_t
%1 = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, CopyMemoryNoAccessGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), Eq(""));
}

TEST_F(ValidateMemory, CopyMemorySimpleMixedAccessGood) {
  // Test one memory access operand using features that don't require the
  // Vulkan memory model.
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Volatile|Aligned|Nontemporal 4
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), Eq(""));
}

TEST_F(ValidateMemory, CopyMemorySimpleTwoMixedAccessV13Bad) {
  // Two memory access operands is invalid up to SPIR-V 1.3
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Volatile Volatile
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("CopyMemory with two memory access operands requires "
                        "SPIR-V 1.4 or later"));
}

TEST_F(ValidateMemory, CopyMemorySimpleTwoMixedAccessV14Good) {
  // Two memory access operands is valid in SPIR-V 1.4
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemory %var1 %var2 Volatile Volatile
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(), Eq(""));
}

TEST_F(ValidateMemory, CopyMemorySizedNoAccessGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Addresses
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_16 = OpConstant %int 16
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_16
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(), Eq(""));
}

TEST_F(ValidateMemory, CopyMemorySizedSimpleMixedAccessGood) {
  // Test one memory access operand using features that don't require the
  // Vulkan memory model.
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Addresses
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_16 = OpConstant %int 16
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_16 Volatile|Aligned|Nontemporal 4
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, CopyMemorySizedSimpleTwoMixedAccessV13Bad) {
  // Two memory access operands is invalid up to SPIR-V 1.3
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Addresses
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_16 = OpConstant %int 16
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_16 Volatile Volatile
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("CopyMemorySized with two memory access operands requires "
                "SPIR-V 1.4 or later"));
}

TEST_F(ValidateMemory, CopyMemorySizedSimpleTwoMixedAccessV14Good) {
  // Two memory access operands is valid in SPIR-V 1.4
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Addresses
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_16 = OpConstant %int 16
%int_ptr_priv = OpTypePointer Private %int
%var1 = OpVariable %int_ptr_priv Private
%var2 = OpVariable %int_ptr_priv Private
%voidfn = OpTypeFunction %void
%func = OpFunction %void None %voidfn
%entry = OpLabel
OpCopyMemorySized %var1 %var2 %int_16 Volatile Volatile
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(), Eq(""));
}

using ValidatePointerComparisons = spvtest::ValidateBase<std::string>;

TEST_P(ValidatePointerComparisons, Good) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer StorageBuffer %int
%var = OpVariable %ptr_int StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_P(ValidatePointerComparisons, GoodWorkgroup) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointers
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer Workgroup %int
%var = OpVariable %ptr_int Workgroup
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_P(ValidatePointerComparisons, BadResultType) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer StorageBuffer %int
%var = OpVariable %ptr_int StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %bool ";
  } else {
    spirv += " %int ";
  }

  spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  if (operation == "OpPtrDiff") {
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("Result Type must be an integer scalar"));
  } else {
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("Result Type must be OpTypeBool"));
  }
}

TEST_P(ValidatePointerComparisons, BadCapabilities) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer StorageBuffer %int
%var = OpVariable %ptr_int StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  if (operation == "OpPtrDiff") {
    // Gets caught by the grammar.
    EXPECT_EQ(SPV_ERROR_INVALID_CAPABILITY,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  } else {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("Instruction on logical pointers cannot be "
                          "used without a variable pointers capability"));
  }
}

TEST_P(ValidatePointerComparisons, BadOperandType) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer StorageBuffer %int
%var = OpVariable %ptr_int StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%ld = OpLoad %int %var
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%ld %ld
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Operand type must be a pointer"));
}

TEST_P(ValidatePointerComparisons, BadStorageClassWorkgroup) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer Workgroup %int
%var = OpVariable %ptr_int Workgroup
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Workgroup storage class pointer requires "
                        "VariablePointers capability to be specified"));
}

TEST_P(ValidatePointerComparisons, BadStorageClass) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr_int = OpTypePointer Private %int
%var = OpVariable %ptr_int Private
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Invalid pointer storage class"));
}

TEST_P(ValidatePointerComparisons, BadDiffOperandTypes) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%float = OpTypeFloat 32
%ptr_int = OpTypePointer Private %int
%var = OpVariable %ptr_int Private
%ptr_float = OpTypePointer Private %float
%var2 = OpVariable %ptr_float Private
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%ld = OpLoad %int %var
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("The types of Operand 1 and Operand 2 must match"));
}

TEST_P(ValidatePointerComparisons, GoodUntypedPointerSameType) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_P(ValidatePointerComparisons, GoodUntypedPointerSameStorageClass) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr1 StorageBuffer
%ptr2 = OpTypeUntypedPointerKHR StorageBuffer
%var2 = OpUntypedVariableKHR %ptr2 StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  if (operation == "OpPtrDiff") {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("The types of Operand 1 and Operand 2 must match"));
  } else {
    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  }
}

TEST_P(ValidatePointerComparisons, BadUntypedPointerDiffStorageClass) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR StorageBuffer
%var1 = OpUntypedVariableKHR %ptr1 StorageBuffer
%ptr2 = OpTypeUntypedPointerKHR Workgroup
%var2 = OpUntypedVariableKHR %ptr2 Workgroup %int
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var1 %var2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  if (operation == "OpPtrDiff") {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("The types of Operand 1 and Operand 2 must match"));
  } else {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("Pointer storage classes must match"));
  }
}

TEST_P(ValidatePointerComparisons, GoodMixedPointerSameStorageClass) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr1 StorageBuffer
%ptr2 = OpTypePointer StorageBuffer %int
%var2 = OpVariable %ptr2 StorageBuffer
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var %var2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  if (operation == "OpPtrDiff") {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("The types of Operand 1 and Operand 2 must match"));
  } else {
    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  }
}

TEST_P(ValidatePointerComparisons, BadMixedPointerDiffStorageClass) {
  const std::string operation = GetParam();

  std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%ptr1 = OpTypeUntypedPointerKHR StorageBuffer
%var1 = OpUntypedVariableKHR %ptr1 StorageBuffer
%ptr2 = OpTypePointer Workgroup %int
%var2 = OpVariable %ptr2 Workgroup
%func_ty = OpTypeFunction %void
%func = OpFunction %void None %func_ty
%1 = OpLabel
%equal = )" + operation;

  if (operation == "OpPtrDiff") {
    spirv += " %int ";
  } else {
    spirv += " %bool ";
  }

  spirv += R"(%var1 %var2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  if (operation == "OpPtrDiff") {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("The types of Operand 1 and Operand 2 must match"));
  } else {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("Pointer storage classes must match"));
  }
}

INSTANTIATE_TEST_SUITE_P(PointerComparisons, ValidatePointerComparisons,
                         Values("OpPtrEqual", "OpPtrNotEqual", "OpPtrDiff"));

TEST_F(ValidateMemory, VariableInitializerWrongType) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointersStorageBuffer
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%int = OpTypeInt 32 0
%float = OpTypeFloat 32
%ptr_wg_int = OpTypePointer Workgroup %int
%ptr_wg_float = OpTypePointer Workgroup %int
%wg_var = OpVariable %ptr_wg_int Workgroup
%ptr_private_wg_float = OpTypePointer Private %ptr_wg_float
%priv_var = OpVariable %ptr_private_wg_float Private %wg_var
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Initializer type must match the data type"));
}

TEST_F(ValidateMemory, StoreToImage) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%int = OpTypeInt 32 0
%img = OpTypeImage %int 2D 2 0 0 2 R32i
%ptr_img = OpTypePointer Function %img
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_img Function
%value = OpLoad %img %var
OpStore %var %value
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeImage-06924"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Cannot store to OpTypeImage, OpTypeSampler, "
                "OpTypeSampledImage, or OpTypeAccelerationStructureKHR"));
}

TEST_F(ValidateMemory, StoreToImageArray) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%int = OpTypeInt 32 0
%img = OpTypeImage %int 2D 2 0 0 2 R32i
%arr_size = OpConstant %int 5
%i = OpConstant %int 2
%arr_img = OpTypeArray %img %arr_size
%ptr_img = OpTypePointer Function %img
%ptr_arr_img = OpTypePointer Function %arr_img
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_arr_img Function
%value = OpLoad %arr_img %var
OpStore %var %value
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeImage-06924"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Cannot store to OpTypeImage, OpTypeSampler, "
                "OpTypeSampledImage, or OpTypeAccelerationStructureKHR"));
}

TEST_F(ValidateMemory, StoreToSampler) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%smp = OpTypeSampler
%ptr_smp = OpTypePointer Function %smp
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_smp Function
%value = OpLoad %smp %var
OpStore %var %value
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeImage-06924"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Cannot store to OpTypeImage, OpTypeSampler, "
                "OpTypeSampledImage, or OpTypeAccelerationStructureKHR"));
}

TEST_F(ValidateMemory, StoreToSampledImage) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%int = OpTypeInt 32 0
%img = OpTypeImage %int 2D 2 0 0 1 R32i
%samp_img = OpTypeSampledImage %img
%ptr_samp_img = OpTypePointer Function %samp_img
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_samp_img Function
%value = OpLoad %samp_img %var
OpStore %var %value
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeImage-06924"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Cannot store to OpTypeImage, OpTypeSampler, "
                "OpTypeSampledImage, or OpTypeAccelerationStructureKHR"));
}

TEST_F(ValidateMemory, StoreToAccelarationStructureKHR) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability RayQueryKHR
OpExtension "SPV_KHR_ray_query"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%as = OpTypeAccelerationStructureKHR
%ptr_as = OpTypePointer Function %as
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_as Function
%value = OpLoad %as %var
OpStore %var %value
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpTypeImage-06924"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Cannot store to OpTypeImage, OpTypeSampler, "
                "OpTypeSampledImage, or OpTypeAccelerationStructureKHR"));
}

TEST_F(ValidateMemory, StoreToUniformBlock) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int4 = OpTypeVector %int 4
%struct = OpTypeStruct %int4
%ptr_uniform_struct = OpTypePointer Uniform %struct
%ptr_uniform_int4 = OpTypePointer Uniform %int4
%ptr_uniform_int = OpTypePointer Uniform %int
%var = OpVariable %ptr_uniform_struct Uniform
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0
%gep2 = OpAccessChain %ptr_uniform_int %gep1 %int_0
OpStore %gep2 %int_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, StoreToUniformBlockVulkan) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int4 = OpTypeVector %int 4
%struct = OpTypeStruct %int4
%ptr_uniform_struct = OpTypePointer Uniform %struct
%ptr_uniform_int4 = OpTypePointer Uniform %int4
%ptr_uniform_int = OpTypePointer Uniform %int
%var = OpVariable %ptr_uniform_struct Uniform
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0
%gep2 = OpAccessChain %ptr_uniform_int %gep1 %int_0
OpStore %gep2 %int_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
}

// This test requires that the struct is not id 2.
TEST_F(ValidateMemory, StoreToUniformBlockVulkan2) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %gid_var
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %3 Block
OpMemberDecorate %3 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %gid_var BuiltIn GlobalInvocationId
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int3 = OpTypeVector %int 3
%int4 = OpTypeVector %int 4
%3 = OpTypeStruct %int4
%ptr_uniform_struct = OpTypePointer Uniform %3
%ptr_uniform_int4 = OpTypePointer Uniform %int4
%ptr_uniform_int = OpTypePointer Uniform %int
%var = OpVariable %ptr_uniform_struct Uniform
%ptr_input_int3 = OpTypePointer Input %int3
%gid_var = OpVariable %ptr_input_int3 Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0
%gep2 = OpAccessChain %ptr_uniform_int %gep1 %int_0
OpStore %gep2 %int_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
}

TEST_F(ValidateMemory, StoreToUniformBufferBlockVulkan) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct BufferBlock
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int4 = OpTypeVector %int 4
%struct = OpTypeStruct %int4
%ptr_uniform_struct = OpTypePointer Uniform %struct
%ptr_uniform_int4 = OpTypePointer Uniform %int4
%ptr_uniform_int = OpTypePointer Uniform %int
%var = OpVariable %ptr_uniform_struct Uniform
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0
%gep2 = OpAccessChain %ptr_uniform_int %gep1 %int_0
OpStore %gep2 %int_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1));
}

TEST_F(ValidateMemory, StoreToUniformBlockVulkanArray) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%int4 = OpTypeVector %int 4
%struct = OpTypeStruct %int4
%array_struct = OpTypeArray %struct %int_1
%ptr_uniform_array = OpTypePointer Uniform %array_struct
%ptr_uniform_struct = OpTypePointer Uniform %struct
%ptr_uniform_int4 = OpTypePointer Uniform %int4
%ptr_uniform_int = OpTypePointer Uniform %int
%var = OpVariable %ptr_uniform_array Uniform
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep1 = OpAccessChain %ptr_uniform_int %var %int_0 %int_0 %int_0
%gep2 = OpCopyObject %ptr_uniform_int %gep1
OpStore %gep2 %int_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
}

// This test requires that the struct is not id 2.
TEST_F(ValidateMemory, StoreToUniformBlockVulkanArray2) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %gid_var
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %gid_var BuiltIn GlobalInvocationId
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%int3 = OpTypeVector %int 3
%int4 = OpTypeVector %int 4
%struct = OpTypeStruct %int4
%array_struct = OpTypeArray %struct %int_1
%ptr_uniform_array = OpTypePointer Uniform %array_struct
%ptr_uniform_struct = OpTypePointer Uniform %struct
%ptr_uniform_int4 = OpTypePointer Uniform %int4
%ptr_uniform_int = OpTypePointer Uniform %int
%var = OpVariable %ptr_uniform_array Uniform
%ptr_input_int3 = OpTypePointer Input %int3
%gid_var = OpVariable %ptr_input_int3 Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep1 = OpAccessChain %ptr_uniform_int %var %int_0 %int_0 %int_0
%gep2 = OpCopyObject %ptr_uniform_int %gep1
OpStore %gep2 %int_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
}

TEST_F(ValidateMemory, StoreToUniformBlockVulkanRuntimeArray) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability RuntimeDescriptorArrayEXT
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int4 = OpTypeVector %int 4
%struct = OpTypeStruct %int4
%array_struct = OpTypeRuntimeArray %struct
%ptr_uniform_array = OpTypePointer Uniform %array_struct
%ptr_uniform_struct = OpTypePointer Uniform %struct
%ptr_uniform_int4 = OpTypePointer Uniform %int4
%ptr_uniform_int = OpTypePointer Uniform %int
%var = OpVariable %ptr_uniform_array Uniform
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep1 = OpAccessChain %ptr_uniform_int4 %var %int_0 %int_0
%gep2 = OpInBoundsAccessChain %ptr_uniform_int %gep1 %int_0
OpStore %gep2 %int_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06925"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("In the Vulkan environment, cannot store to Uniform Blocks"));
}

using ValidateSizedVariable = spvtest::ValidateBase<
    std::tuple<std::string, std::string, std::string, spv_target_env>>;

CodeGenerator GetSizedVariableCodeGenerator(bool is_8bit, bool buffer_block) {
  CodeGenerator generator;
  generator.capabilities_ = "OpCapability Shader\nOpCapability Linkage\n";
  generator.extensions_ =
      "OpExtension \"SPV_KHR_16bit_storage\"\nOpExtension "
      "\"SPV_KHR_8bit_storage\"\n";
  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
  if (is_8bit) {
    generator.before_types_ =
        "OpMemberDecorate %char_buffer_block 0 Offset 0\n";
    if (buffer_block)
      generator.before_types_ += "OpDecorate %char_buffer_block BufferBlock\n";

    generator.types_ = R"(%void = OpTypeVoid
%char = OpTypeInt 8 0
%char4 = OpTypeVector %char 4
%char_buffer_block = OpTypeStruct %char
)";
  } else {
    generator.before_types_ =
        "OpMemberDecorate %half_buffer_block 0 Offset 0\n"
        "OpMemberDecorate %short_buffer_block 0 Offset 0\n";
    if (buffer_block) {
      generator.before_types_ +=
          "OpDecorate %half_buffer_block BufferBlock\n"
          "OpDecorate %short_buffer_block BufferBlock\n";
    }

    generator.types_ = R"(%void = OpTypeVoid
%short = OpTypeInt 16 0
%half = OpTypeFloat 16
%short4 = OpTypeVector %short 4
%half4 = OpTypeVector %half 4
%mat4x4 = OpTypeMatrix %half4 4
%short_buffer_block = OpTypeStruct %short
%half_buffer_block = OpTypeStruct %half
)";
  }
  generator.after_types_ = R"(%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
)";
  generator.add_at_the_end_ = "OpReturn\nOpFunctionEnd\n";
  return generator;
}

TEST_P(ValidateSizedVariable, Capability) {
  const std::string storage_class = std::get<0>(GetParam());
  const std::string capability = std::get<1>(GetParam());
  const std::string var_type = std::get<2>(GetParam());
  const spv_target_env target = std::get<3>(GetParam());

  ASSERT_TRUE(target == SPV_ENV_UNIVERSAL_1_3 ||
              target == SPV_ENV_UNIVERSAL_1_4);

  bool type_8bit = false;
  if (var_type == "%char" || var_type == "%char4" ||
      var_type == "%char_buffer_block") {
    type_8bit = true;
  }

  const bool buffer_block = var_type.find("buffer_block") != std::string::npos;

  auto generator = GetSizedVariableCodeGenerator(type_8bit, buffer_block);

  if (capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR" ||
      capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR") {
    generator.extensions_ +=
        "OpExtension \"SPV_KHR_workgroup_memory_explicit_layout\"\n";
  }

  generator.types_ += "%ptr_type = OpTypePointer " + storage_class + " " +
                      var_type + "\n%var = OpVariable %ptr_type " +
                      storage_class + "\n";
  generator.capabilities_ += "OpCapability " + capability + "\n";

  bool capability_ok = false;
  bool storage_class_ok = false;
  if (storage_class == "Input" || storage_class == "Output") {
    if (!type_8bit) {
      capability_ok = capability == "StorageInputOutput16";
      storage_class_ok = true;
    }
  } else if (storage_class == "StorageBuffer") {
    if (type_8bit) {
      capability_ok = capability == "StorageBuffer8BitAccess" ||
                      capability == "UniformAndStorageBuffer8BitAccess";
    } else {
      capability_ok = capability == "StorageBuffer16BitAccess" ||
                      capability == "UniformAndStorageBuffer16BitAccess";
    }
    storage_class_ok = true;
  } else if (storage_class == "PushConstant") {
    if (type_8bit) {
      capability_ok = capability == "StoragePushConstant8";
    } else {
      capability_ok = capability == "StoragePushConstant16";
    }
    storage_class_ok = true;
  } else if (storage_class == "Uniform") {
    if (type_8bit) {
      capability_ok = capability == "UniformAndStorageBuffer8BitAccess" ||
                      (capability == "StorageBuffer8BitAccess" && buffer_block);
    } else {
      capability_ok =
          capability == "UniformAndStorageBuffer16BitAccess" ||
          (capability == "StorageBuffer16BitAccess" && buffer_block);
    }
    storage_class_ok = true;
  } else if (storage_class == "Workgroup") {
    if (type_8bit) {
      capability_ok =
          capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR";
    } else {
      capability_ok =
          capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR";
    }
    storage_class_ok = true;
  }

  CompileSuccessfully(generator.Build(), target);
  spv_result_t result = ValidateInstructions(target);
  if (target < SPV_ENV_UNIVERSAL_1_4 &&
      (capability == "WorkgroupMemoryExplicitLayout8BitAccessKHR" ||
       capability == "WorkgroupMemoryExplicitLayout16BitAccessKHR")) {
    EXPECT_EQ(SPV_ERROR_WRONG_VERSION, result);
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("requires SPIR-V version 1.4 or later"));
  } else if (buffer_block && target > SPV_ENV_UNIVERSAL_1_3) {
    EXPECT_EQ(SPV_ERROR_WRONG_VERSION, result);
    EXPECT_THAT(getDiagnosticString(),
                HasSubstr("requires SPIR-V version 1.3 or earlier"));
  } else if (capability_ok) {
    EXPECT_EQ(SPV_SUCCESS, result);
  } else {
    EXPECT_EQ(SPV_ERROR_INVALID_ID, result);
    if (storage_class_ok) {
      std::string message = std::string("Allocating a variable containing a ") +
                            (type_8bit ? "8" : "16") + "-bit element in " +
                            storage_class +
                            " storage class requires an additional capability";
      EXPECT_THAT(getDiagnosticString(), HasSubstr(message));
    } else {
      std::string message =
          std::string("Cannot allocate a variable containing a ") +
          (type_8bit ? "8" : "16") + "-bit type in " + storage_class +
          " storage class";
      EXPECT_THAT(getDiagnosticString(), HasSubstr(message));
    }
  }
}

INSTANTIATE_TEST_SUITE_P(
    Storage8, ValidateSizedVariable,
    Combine(Values("UniformConstant", "Input", "Output", "Workgroup",
                   "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"),
            Values("StorageBuffer8BitAccess",
                   "UniformAndStorageBuffer8BitAccess", "StoragePushConstant8",
                   "WorkgroupMemoryExplicitLayout8BitAccessKHR"),
            Values("%char", "%char4", "%char_buffer_block"),
            Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4)));

INSTANTIATE_TEST_SUITE_P(
    Storage16, ValidateSizedVariable,
    Combine(Values("UniformConstant", "Input", "Output", "Workgroup",
                   "CrossWorkgroup", "Private", "StorageBuffer", "Uniform"),
            Values("StorageBuffer16BitAccess",
                   "UniformAndStorageBuffer16BitAccess",
                   "StoragePushConstant16", "StorageInputOutput16",
                   "WorkgroupMemoryExplicitLayout16BitAccessKHR"),
            Values("%short", "%half", "%short4", "%half4", "%mat4x4",
                   "%short_buffer_block", "%half_buffer_block"),
            Values(SPV_ENV_UNIVERSAL_1_3, SPV_ENV_UNIVERSAL_1_4)));

using ValidateSizedLoadStore =
    spvtest::ValidateBase<std::tuple<std::string, uint32_t, std::string>>;

CodeGenerator GetSizedLoadStoreCodeGenerator(const std::string& base_type,
                                             uint32_t width) {
  CodeGenerator generator;
  generator.capabilities_ = "OpCapability Shader\nOpCapability Linkage\n";
  if (width == 8) {
    generator.capabilities_ +=
        "OpCapability UniformAndStorageBuffer8BitAccess\n";
    generator.extensions_ = "OpExtension \"SPV_KHR_8bit_storage\"\n";
  } else {
    generator.capabilities_ +=
        "OpCapability UniformAndStorageBuffer16BitAccess\n";
    generator.extensions_ = "OpExtension \"SPV_KHR_16bit_storage\"\n";
  }
  generator.memory_model_ = "OpMemoryModel Logical GLSL450\n";
  generator.before_types_ = R"(OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpMemberDecorate %struct 0 Offset 0
)";
  generator.types_ = R"(%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_1 = OpConstant %int 1
%int_2 = OpConstant %int 2
%int_3 = OpConstant %int 3
)";

  if (width == 8) {
    generator.types_ += R"(%scalar = OpTypeInt 8 0
%vector = OpTypeVector %scalar 4
%struct = OpTypeStruct %vector
)";
  } else if (base_type == "int") {
    generator.types_ += R"(%scalar = OpTypeInt 16 0
%vector = OpTypeVector %scalar 4
%struct = OpTypeStruct %vector
)";
  } else {
    generator.types_ += R"(%scalar = OpTypeFloat 16
%vector = OpTypeVector %scalar 4
%matrix = OpTypeMatrix %vector 4
%struct = OpTypeStruct %matrix
%ptr_ssbo_matrix = OpTypePointer StorageBuffer %matrix
)";
    generator.before_types_ += R"(OpMemberDecorate %struct 0 RowMajor
OpMemberDecorate %struct 0 MatrixStride 16
)";
  }
  generator.types_ += R"(%block = OpTypeStruct %struct
%ptr_ssbo_block = OpTypePointer StorageBuffer %block
%ptr_ssbo_struct = OpTypePointer StorageBuffer %struct
%ptr_ssbo_vector = OpTypePointer StorageBuffer %vector
%ptr_ssbo_scalar = OpTypePointer StorageBuffer %scalar
%ld_var = OpVariable %ptr_ssbo_block StorageBuffer
%st_var = OpVariable %ptr_ssbo_block StorageBuffer
)";

  generator.after_types_ = R"(%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
)";
  generator.add_at_the_end_ = "OpReturn\nOpFunctionEnd\n";
  return generator;
}

TEST_P(ValidateSizedLoadStore, Load) {
  std::string base_type = std::get<0>(GetParam());
  uint32_t width = std::get<1>(GetParam());
  std::string mem_type = std::get<2>(GetParam());

  CodeGenerator generator = GetSizedLoadStoreCodeGenerator(base_type, width);
  generator.after_types_ +=
      "%ld_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %ld_var %int_0";
  if (mem_type != "struct") {
    generator.after_types_ += " %int_0";
    if (mem_type != "matrix" && base_type == "float") {
      generator.after_types_ += " %int_0";
    }
    if (mem_type == "scalar") {
      generator.after_types_ += " %int_0";
    }
  }
  generator.after_types_ += "\n";
  generator.after_types_ += "%ld = OpLoad %" + mem_type + " %ld_gep\n";

  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
  if (mem_type == "struct") {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
    EXPECT_THAT(
        getDiagnosticString(),
        HasSubstr(
            "8- or 16-bit loads must be a scalar, vector or matrix type"));
  } else {
    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  }
}

TEST_P(ValidateSizedLoadStore, Store) {
  std::string base_type = std::get<0>(GetParam());
  uint32_t width = std::get<1>(GetParam());
  std::string mem_type = std::get<2>(GetParam());

  CodeGenerator generator = GetSizedLoadStoreCodeGenerator(base_type, width);
  generator.after_types_ +=
      "%ld_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %ld_var %int_0";
  if (mem_type != "struct") {
    generator.after_types_ += " %int_0";
    if (mem_type != "matrix" && base_type == "float") {
      generator.after_types_ += " %int_0";
    }
    if (mem_type == "scalar") {
      generator.after_types_ += " %int_0";
    }
  }
  generator.after_types_ += "\n";
  generator.after_types_ += "%ld = OpLoad %" + mem_type + " %ld_gep\n";
  generator.after_types_ +=
      "%st_gep = OpAccessChain %ptr_ssbo_" + mem_type + " %st_var %int_0";
  if (mem_type != "struct") {
    generator.after_types_ += " %int_0";
    if (mem_type != "matrix" && base_type == "float") {
      generator.after_types_ += " %int_0";
    }
    if (mem_type == "scalar") {
      generator.after_types_ += " %int_0";
    }
  }
  generator.after_types_ += "\n";
  generator.after_types_ += "OpStore %st_gep %ld\n";

  CompileSuccessfully(generator.Build(), SPV_ENV_UNIVERSAL_1_3);
  if (mem_type == "struct") {
    EXPECT_EQ(SPV_ERROR_INVALID_ID,
              ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
    // Can only catch the load.
    EXPECT_THAT(
        getDiagnosticString(),
        HasSubstr(
            "8- or 16-bit loads must be a scalar, vector or matrix type"));
  } else {
    EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  }
}

INSTANTIATE_TEST_SUITE_P(LoadStoreInt8, ValidateSizedLoadStore,
                         Combine(Values("int"), Values(8u),
                                 Values("scalar", "vector", "struct")));
INSTANTIATE_TEST_SUITE_P(LoadStoreInt16, ValidateSizedLoadStore,
                         Combine(Values("int"), Values(16u),
                                 Values("scalar", "vector", "struct")));
INSTANTIATE_TEST_SUITE_P(LoadStoreFloat16, ValidateSizedLoadStore,
                         Combine(Values("float"), Values(16u),
                                 Values("scalar", "vector", "matrix",
                                        "struct")));

TEST_F(ValidateMemory, SmallStorageCopyMemoryChar) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UniformAndStorageBuffer8BitAccess
OpExtension "SPV_KHR_8bit_storage"
OpMemoryModel Logical GLSL450
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%char = OpTypeInt 8 0
%block = OpTypeStruct %char
%ptr_ssbo_block = OpTypePointer StorageBuffer %block
%in = OpVariable %ptr_ssbo_block StorageBuffer
%out = OpVariable %ptr_ssbo_block StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %out %in
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types"));
}

TEST_F(ValidateMemory, SmallStorageCopyMemoryShort) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UniformAndStorageBuffer16BitAccess
OpExtension "SPV_KHR_16bit_storage"
OpMemoryModel Logical GLSL450
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%short = OpTypeInt 16 0
%block = OpTypeStruct %short
%ptr_ssbo_block = OpTypePointer StorageBuffer %block
%in = OpVariable %ptr_ssbo_block StorageBuffer
%out = OpVariable %ptr_ssbo_block StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %out %in
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types"));
}

TEST_F(ValidateMemory, SmallStorageCopyMemoryHalf) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UniformAndStorageBuffer16BitAccess
OpExtension "SPV_KHR_16bit_storage"
OpMemoryModel Logical GLSL450
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%half = OpTypeFloat 16
%block = OpTypeStruct %half
%ptr_ssbo_block = OpTypePointer StorageBuffer %block
%in = OpVariable %ptr_ssbo_block StorageBuffer
%out = OpVariable %ptr_ssbo_block StorageBuffer
%void_fn = OpTypeFunction %void
%func = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %out %in
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("Cannot copy memory of objects containing 8- or 16-bit types"));
}

TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockShort) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability StorageBuffer16BitAccess
OpExtension "SPV_KHR_16bit_storage"
OpMemoryModel Logical GLSL450
OpDecorate %block BufferBlock
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%short = OpTypeInt 16 0
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%block = OpTypeStruct %short
%block_array = OpTypeArray %block %int_4
%ptr_block_array = OpTypePointer Uniform %block_array
%var = OpVariable %ptr_block_array Uniform
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockChar) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability StorageBuffer8BitAccess
OpExtension "SPV_KHR_8bit_storage"
OpMemoryModel Logical GLSL450
OpDecorate %block BufferBlock
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%char = OpTypeInt 8 0
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%block = OpTypeStruct %char
%block_array = OpTypeArray %block %int_4
%ptr_block_array = OpTypePointer Uniform %block_array
%var = OpVariable %ptr_block_array Uniform
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, SmallStorageVariableArrayBufferBlockHalf) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability StorageBuffer16BitAccess
OpExtension "SPV_KHR_16bit_storage"
OpMemoryModel Logical GLSL450
OpDecorate %block BufferBlock
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%half = OpTypeFloat 16
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%block = OpTypeStruct %half
%block_array = OpTypeArray %block %int_4
%ptr_block_array = OpTypePointer Uniform %block_array
%var = OpVariable %ptr_block_array Uniform
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, VulkanStorageBufferNotAStruct) {
  const std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%ptr_ssbo = OpTypePointer StorageBuffer %uint
%var = OpVariable %ptr_ssbo StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("From Vulkan spec:\nVariables identified with "
                "the StorageBuffer storage class are used to access "
                "transparent buffer backed resources. Such variables must be "
                "typed as OpTypeStruct, or an array of this type"));
}

TEST_F(ValidateMemory, VulkanStorageBufferRuntimeArrayNotAStruct) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability RuntimeDescriptorArrayEXT
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_EXT_descriptor_indexing"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%array = OpTypeRuntimeArray %uint
%ptr_ssbo = OpTypePointer StorageBuffer %array
%var = OpVariable %ptr_ssbo StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("From Vulkan spec:\nVariables identified with "
                "the StorageBuffer storage class are used to access "
                "transparent buffer backed resources. Such variables must be "
                "typed as OpTypeStruct, or an array of this type"));
}

TEST_F(ValidateMemory, VulkanStorageBufferArrayNotAStruct) {
  const std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_4 = OpConstant %uint 4
%array = OpTypeArray %uint %uint_4
%ptr_ssbo = OpTypePointer StorageBuffer %array
%var = OpVariable %ptr_ssbo StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Uniform-06807"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("From Vulkan spec:\nVariables identified with "
                "the StorageBuffer storage class are used to access "
                "transparent buffer backed resources. Such variables must be "
                "typed as OpTypeStruct, or an array of this type"));
}

TEST_F(ValidateMemory, VulkanInvariantOutputSuccess) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main" %var
OpDecorate %var Location 0
OpDecorate %var Invariant
%void = OpTypeVoid
%f32 = OpTypeFloat 32
%ptr_output = OpTypePointer Output %f32
%var = OpVariable %ptr_output Output
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}

TEST_F(ValidateMemory, VulkanInvariantInputStructSuccess) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main" %var
OpExecutionMode %main OriginUpperLeft
OpDecorate %var Location 0
OpMemberDecorate %struct 1 Invariant
%void = OpTypeVoid
%f32 = OpTypeFloat 32
%struct = OpTypeStruct %f32 %f32
%ptr_input = OpTypePointer Input %struct
%var = OpVariable %ptr_input Input
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}

TEST_F(ValidateMemory, VulkanInvariantWrongStorageClass) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Vertex %main "main"
OpDecorate %var Invariant
%void = OpTypeVoid
%f32 = OpTypeFloat 32
%ptr_private = OpTypePointer Private %f32
%var = OpVariable %ptr_private Private
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Invariant-04677"));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Variable decorated with Invariant must only be identified with the "
          "Input or Output storage class in Vulkan environment."));
}

TEST_F(ValidateMemory, VulkanInvariantMemberWrongStorageClass) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
OpMemberDecorate %struct 1 Invariant
%void = OpTypeVoid
%f32 = OpTypeFloat 32
%struct = OpTypeStruct %f32 %f32
%ptr_private = OpTypePointer Private %struct
%var = OpVariable %ptr_private Private
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_0));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Invariant-04677"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Variable struct member decorated with Invariant must "
                        "only be identified with the Input or Output storage "
                        "class in Vulkan environment."));
}

TEST_F(ValidateMemory, PhysicalStorageBufferPtrEqual) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Int64
OpCapability PhysicalStorageBufferAddresses
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%bool = OpTypeBool
%long = OpTypeInt 64 0
%long_0 = OpConstant %long 0
%ptr_pssbo_long = OpTypePointer PhysicalStorageBuffer %long
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%conv = OpConvertUToPtr %ptr_pssbo_long %long_0
%eq = OpPtrEqual %bool %conv %conv
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Cannot use a pointer in the PhysicalStorageBuffer storage class"));
}

TEST_F(ValidateMemory, PhysicalStorageBufferPtrNotEqual) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Int64
OpCapability PhysicalStorageBufferAddresses
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%bool = OpTypeBool
%long = OpTypeInt 64 0
%long_0 = OpConstant %long 0
%ptr_pssbo_long = OpTypePointer PhysicalStorageBuffer %long
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%conv = OpConvertUToPtr %ptr_pssbo_long %long_0
%neq = OpPtrNotEqual %bool %conv %conv
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Cannot use a pointer in the PhysicalStorageBuffer storage class"));
}

TEST_F(ValidateMemory, PhysicalStorageBufferPtrDiff) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Int64
OpCapability PhysicalStorageBufferAddresses
OpCapability VariablePointers
OpMemoryModel PhysicalStorageBuffer64 GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%long = OpTypeInt 64 0
%long_0 = OpConstant %long 0
%ptr_pssbo_long = OpTypePointer PhysicalStorageBuffer %long
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%conv = OpConvertUToPtr %ptr_pssbo_long %long_0
%diff = OpPtrDiff %long %conv %conv
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Cannot use a pointer in the PhysicalStorageBuffer storage class"));
}

TEST_F(ValidateMemory, VulkanInitializerWithWorkgroupStorageClassBad) {
  std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Workgroup %float
%init_val = OpConstant %float 1.0
%1 = OpVariable %float_ptr Workgroup %init_val
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_3));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID(" VUID-StandaloneSpirv-OpVariable-04734"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpVariable, <id> '5[%5]', initializers are limited to "
                        "OpConstantNull in Workgroup storage class"));
}

TEST_F(ValidateMemory, VulkanInitializerWithWorkgroupStorageClassGood) {
  std::string spirv = R"(
OpCapability Shader
OpCapability VulkanMemoryModelKHR
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%float = OpTypeFloat 32
%float_ptr = OpTypePointer Workgroup %float
%init_val = OpConstantNull %float
%1 = OpVariable %float_ptr Workgroup %init_val
%void = OpTypeVoid
%functy = OpTypeFunction %void
%func = OpFunction %void None %functy
%2 = OpLabel
OpReturn
OpFunctionEnd
)";
  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}

TEST_F(ValidateMemory, LoadRuntimeArray) {
  const std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%rta = OpTypeRuntimeArray %int
%block = OpTypeStruct %rta
%ptr_rta = OpTypePointer StorageBuffer %rta
%ptr_block = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_block StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpAccessChain %ptr_rta %var %int_0
%ld = OpLoad %rta %gep
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Cannot load a runtime-sized array"));
}

TEST_F(ValidateMemory, LoadRuntimeArrayInStruct) {
  const std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%rta = OpTypeRuntimeArray %int
%block = OpTypeStruct %rta
%ptr_rta = OpTypePointer StorageBuffer %rta
%ptr_block = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_block StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%ld = OpLoad %block %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Cannot load a runtime-sized array"));
}

TEST_F(ValidateMemory, LoadRuntimeArrayInArray) {
  const std::string spirv = R"(
OpCapability Shader
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%int_4 = OpConstant %int 4
%rta = OpTypeRuntimeArray %int
%block = OpTypeStruct %rta
%array = OpTypeArray %block %int_4
%ptr_rta = OpTypePointer StorageBuffer %rta
%ptr_block = OpTypePointer StorageBuffer %block
%ptr_array = OpTypePointer StorageBuffer %array
%var = OpVariable %ptr_array StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%ld = OpLoad %array %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Cannot load a runtime-sized array"));
}

TEST_F(ValidateMemory, Pre1p4WorkgroupMemoryBadLayoutOk) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%bool = OpTypeBool
%struct = OpTypeStruct %bool
%ptr = OpTypePointer Workgroup %struct
%var = OpVariable %ptr Workgroup
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, UntypedVariableGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Private
%var = OpUntypedVariableKHR %ptr Private %int %int_0
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, UntypedVariableNoDataType) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_F(ValidateMemory, UntypedVariableNoDataTypeFunction) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Function
%var = OpUntypedVariableKHR %ptr Function
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Data type must be specified for Function, "
                        "Private, and Workgroup storage classes"));
}

TEST_F(ValidateMemory, UntypedVariableNoDataTypePrivate) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Private
%var = OpUntypedVariableKHR %ptr Private
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Data type must be specified for Function, "
                        "Private, and Workgroup storage classes"));
}

TEST_F(ValidateMemory, UntypedVariableNoDataTypeWorkgroup) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Data type must be specified for Function, "
                        "Private, and Workgroup storage classes"));
}

TEST_F(ValidateMemory, UntypedVariableNoDataTypeVulkan) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-OpUntypedVariableKHR-11167"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Vulkan requires that data type be specified"));
}

TEST_F(ValidateMemory, PtrAccessChainArrayStrideBad) {
  const std::string spirv = R"(
               OpCapability Shader
               OpCapability VariablePointersStorageBuffer
               OpExtension "SPV_KHR_storage_buffer_storage_class"
               OpExtension "SPV_KHR_variable_pointers"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %main "foo" %var
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %var DescriptorSet 0
               OpDecorate %var Binding 0
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
        %ptr = OpTypePointer StorageBuffer %uint
       %void = OpTypeVoid
       %func = OpTypeFunction %void
        %var = OpVariable %ptr StorageBuffer
       %main = OpFunction %void None %func
      %label = OpLabel
     %access = OpAccessChain %ptr %var
 %ptr_access = OpPtrAccessChain %ptr %access %uint_1
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpPtrAccessChain must have a Base whose type is "
                        "decorated with ArrayStride"));
}

TEST_F(ValidateMemory, PtrAccessChainArrayStrideSuccess) {
  const std::string spirv = R"(
               OpCapability Shader
               OpCapability VariablePointersStorageBuffer
               OpExtension "SPV_KHR_storage_buffer_storage_class"
               OpExtension "SPV_KHR_variable_pointers"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %main "foo" %var
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %var DescriptorSet 0
               OpDecorate %var Binding 00
               OpDecorate %ptr ArrayStride 4
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
        %ptr = OpTypePointer StorageBuffer %uint
       %void = OpTypeVoid
       %func = OpTypeFunction %void
        %var = OpVariable %ptr StorageBuffer
       %main = OpFunction %void None %func
      %label = OpLabel
     %access = OpAccessChain %ptr %var
 %ptr_access = OpPtrAccessChain %ptr %access %uint_1
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_5);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_5));
}

TEST_F(ValidateMemory, VulkanPtrAccessChainStorageBufferSuccess) {
  const std::string spirv = R"(
               OpCapability Shader
               OpCapability VariablePointersStorageBuffer
               OpExtension "SPV_KHR_storage_buffer_storage_class"
               OpExtension "SPV_KHR_variable_pointers"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %main "foo" %var
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %_runtimearr_uint ArrayStride 4
               OpMemberDecorate %_struct_10 0 Offset 0
               OpDecorate %_struct_10 Block
               OpDecorate %var DescriptorSet 0
               OpDecorate %var Binding 0
               OpDecorate %_ptr_StorageBuffer_uint ArrayStride 4
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
%_runtimearr_uint = OpTypeRuntimeArray %uint
 %_struct_10 = OpTypeStruct %_runtimearr_uint
%_ptr_StorageBuffer__struct_10 = OpTypePointer StorageBuffer %_struct_10
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
       %void = OpTypeVoid
      %func2 = OpTypeFunction %void %_ptr_StorageBuffer_uint
      %func1 = OpTypeFunction %void
         %var = OpVariable %_ptr_StorageBuffer__struct_10 StorageBuffer
     %called = OpFunction %void None %func2
      %param = OpFunctionParameter %_ptr_StorageBuffer_uint
     %label2 = OpLabel
 %ptr_access = OpPtrAccessChain %_ptr_StorageBuffer_uint %param %uint_1
               OpReturn
               OpFunctionEnd
       %main = OpFunction %void None %func1
     %label1 = OpLabel
     %access = OpAccessChain %_ptr_StorageBuffer_uint %var %uint_0 %uint_0
       %call = OpFunctionCall %void %called %access
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_2);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
}

TEST_F(ValidateMemory, VulkanPtrAccessChainStorageBufferCapability) {
  const std::string spirv = R"(
               OpCapability Shader
               OpCapability PhysicalStorageBufferAddresses
               OpExtension "SPV_KHR_storage_buffer_storage_class"
               OpExtension "SPV_KHR_variable_pointers"
               OpMemoryModel PhysicalStorageBuffer64 GLSL450
               OpEntryPoint GLCompute %main "foo" %var
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %_runtimearr_uint ArrayStride 4
               OpMemberDecorate %_struct_10 0 Offset 0
               OpDecorate %_struct_10 Block
               OpDecorate %var DescriptorSet 0
               OpDecorate %var Binding 0
               OpDecorate %_ptr_StorageBuffer_uint ArrayStride 4
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
%_runtimearr_uint = OpTypeRuntimeArray %uint
 %_struct_10 = OpTypeStruct %_runtimearr_uint
%_ptr_StorageBuffer__struct_10 = OpTypePointer StorageBuffer %_struct_10
%_ptr_StorageBuffer_uint = OpTypePointer StorageBuffer %uint
       %void = OpTypeVoid
       %func = OpTypeFunction %void
         %var = OpVariable %_ptr_StorageBuffer__struct_10 StorageBuffer
       %main = OpFunction %void None %func
      %label = OpLabel
     %access = OpAccessChain %_ptr_StorageBuffer_uint %var %uint_0 %uint_0
 %ptr_access = OpPtrAccessChain %_ptr_StorageBuffer_uint %access %uint_1
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_2);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Base-07652"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpPtrAccessChain Base operand pointing to "
                        "StorageBuffer storage class must use VariablePointers "
                        "or VariablePointersStorageBuffer capability"));
}

TEST_F(ValidateMemory, VulkanPtrAccessChainWorkgroupCapability) {
  const std::string spirv = R"(
               OpCapability Shader
               OpCapability VariablePointersStorageBuffer
               OpExtension "SPV_KHR_storage_buffer_storage_class"
               OpExtension "SPV_KHR_variable_pointers"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %main "foo" %var
               OpExecutionMode %main LocalSize 1 1 1
               OpDecorate %_ptr_Workgroup_uint ArrayStride 4
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
%_arr_uint = OpTypeArray %uint %uint_1
%_ptr_Workgroup__arr_uint = OpTypePointer Workgroup %_arr_uint
%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
       %void = OpTypeVoid
       %func = OpTypeFunction %void
        %var = OpVariable %_ptr_Workgroup__arr_uint Workgroup
       %main = OpFunction %void None %func
      %label = OpLabel
     %access = OpAccessChain %_ptr_Workgroup_uint %var %uint_0
 %ptr_access = OpPtrAccessChain %_ptr_Workgroup_uint %access %uint_1
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_2);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(),
              AnyVUID("VUID-StandaloneSpirv-Base-07651"));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpPtrAccessChain Base operand pointing to Workgroup "
                        "storage class must use VariablePointers capability"));
}

TEST_F(ValidateMemory, VulkanPtrAccessChainWorkgroupNoArrayStrideSuccess) {
  const std::string spirv = R"(
               OpCapability Shader
               OpCapability VariablePointers
               OpExtension "SPV_KHR_storage_buffer_storage_class"
               OpExtension "SPV_KHR_variable_pointers"
               OpMemoryModel Logical GLSL450
               OpEntryPoint GLCompute %main "foo" %var
               OpExecutionMode %main LocalSize 1 1 1
       %uint = OpTypeInt 32 0
     %uint_0 = OpConstant %uint 0
     %uint_1 = OpConstant %uint 1
%_arr_uint = OpTypeArray %uint %uint_1
%_ptr_Workgroup__arr_uint = OpTypePointer Workgroup %_arr_uint
%_ptr_Workgroup_uint = OpTypePointer Workgroup %uint
       %void = OpTypeVoid
       %func = OpTypeFunction %void
        %var = OpVariable %_ptr_Workgroup__arr_uint Workgroup
       %main = OpFunction %void None %func
      %label = OpLabel
     %access = OpAccessChain %_ptr_Workgroup_uint %var %uint_0
 %ptr_access = OpPtrAccessChain %_ptr_Workgroup_uint %access %uint_1
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_2);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_2));
}

TEST_F(ValidateMemory, AccessChainNegativeStructIndex32) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 1
%_struct_4 = OpTypeStruct %int %int %int
%_ptr_Function__struct_4 = OpTypePointer Function %_struct_4
%_ptr_Function_int = OpTypePointer Function %int
%int_n224 = OpConstant %int -224
%fn = OpFunction %void Inline %void_fn
%entry = OpLabel
%var = OpVariable %_ptr_Function__struct_4 Function
%gep = OpInBoundsAccessChain %_ptr_Function_int %var %int_n224
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Index at word 4 may not have a negative value"));
}

TEST_F(ValidateMemory, AccessChainNegativeStructIndex64) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability Int64
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 1
%long = OpTypeInt 64 1
%_struct_4 = OpTypeStruct %int %int %int
%_ptr_Function__struct_4 = OpTypePointer Function %_struct_4
%_ptr_Function_int = OpTypePointer Function %int
%long_n224 = OpConstant %long -224
%fn = OpFunction %void Inline %void_fn
%entry = OpLabel
%var = OpVariable %_ptr_Function__struct_4 Function
%gep = OpInBoundsAccessChain %_ptr_Function_int %var %long_n224
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Index at word 4 may not have a negative value"));
}

TEST_F(ValidateMemory, UntypedVariableFunctionOutsideFunction) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Function
%var = OpUntypedVariableKHR %ptr Function %int
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_LAYOUT, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Variables can not have a function[7] storage class "
                        "outside of a function"));
}

TEST_F(ValidateMemory, UntypedVariableBadResultType) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %int Workgroup %int
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Result type must be an untyped pointer"));
}

TEST_F(ValidateMemory, UntypedVariableBadDataType) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %int_0
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Data type must be a type instruction"));
}

TEST_F(ValidateMemory, UntypedVariableBadStorageClass) {
  const std::string spirv = R"(
OpCapability Kernel
OpCapability GenericPointer
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical OpenCL
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Generic
%var = OpUntypedVariableKHR %ptr Generic %int
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_2);
  EXPECT_EQ(SPV_ERROR_INVALID_BINARY,
            ValidateInstructions(SPV_ENV_UNIVERSAL_1_2));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Variable storage class cannot be Generic"));
}

TEST_F(ValidateMemory, UntypedVariableMismatchedStorageClass) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Private %int
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Storage class must match result type storage class"));
}

TEST_F(ValidateMemory, UntypedVariableBadInitializer) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%int = OpTypeInt 32 0
%float = OpTypeFloat 32
%float_0 = OpConstant %float 0
%ptr = OpTypeUntypedPointerKHR Private
%var = OpUntypedVariableKHR %ptr Private %int %float_0
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Initializer type must match the data type"));
}

TEST_F(ValidateMemory, AccessChainBaseUntypedPointer) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %var "var"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_ssbo = OpTypePointer StorageBuffer %block
%ptr_ssbo_int = OpTypePointer StorageBuffer %int
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %int
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpAccessChain %ptr_ssbo_int %var %int_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("The Base <id> '2[%var]' in OpAccessChain "
                        "instruction must be a pointer"));
}

using ValidateMemoryUntypedAccessChain = spvtest::ValidateBase<std::string>;

TEST_P(ValidateMemoryUntypedAccessChain, GoodTypedPointerBase) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";
  const std::string deco = ptr ? "OpDecorate %ptr_ssbo ArrayStride 4" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
)" + deco + R"(
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_ssbo = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_ssbo StorageBuffer
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %block %var )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_P(ValidateMemoryUntypedAccessChain, GoodUntypedPointerBase) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";
  const std::string deco = ptr ? "OpDecorate %ptr ArrayStride 4" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
)" + deco + R"(
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_ssbo = OpTypePointer StorageBuffer %block
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %int
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %block %var )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
}

TEST_P(ValidateMemoryUntypedAccessChain, ResultTypedPointer) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %gep "gep"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_ssbo = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_ssbo StorageBuffer
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%ptr_int = OpTypePointer StorageBuffer %int
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr_int %block %var )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("The Result Type of " + opcode +
                        " <id> '2[%gep]' must be OpTypeUntypedPointer"));
}

TEST_P(ValidateMemoryUntypedAccessChain, BaseTypeNotAType) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %gep "gep"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_ssbo = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_ssbo StorageBuffer
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %int_0 %var )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Base type must be a non-pointer type"));
}

TEST_P(ValidateMemoryUntypedAccessChain, BaseTypedPointer) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %gep "gep"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_ssbo = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_ssbo StorageBuffer
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %ptr_ssbo %var )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Base type must be a non-pointer type"));
}

TEST_P(ValidateMemoryUntypedAccessChain, BaseUntypedPointer) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %gep "gep"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_ssbo = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_ssbo StorageBuffer
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %ptr %var )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Base type must be a non-pointer type"));
}

TEST_P(ValidateMemoryUntypedAccessChain, BaseNotAPointer) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %int_0 "int_0"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_ssbo = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_ssbo StorageBuffer
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %int %int_0 )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("The Base <id> '2[%int_0]' in " + opcode +
                        " instruction must be a pointer"));
}

TEST_P(ValidateMemoryUntypedAccessChain, StorageClassMismatch) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %int_0 "int_0"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_wg = OpTypePointer Workgroup %block
%var = OpVariable %ptr_wg Workgroup
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %block %var )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("The result pointer storage class and base pointer storage "
                "class in " +
                opcode + " do not match"));
}

TEST_P(ValidateMemoryUntypedAccessChain, NonCompositeBase) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %int_0 "int_0"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_wg = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_wg StorageBuffer
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %int %var )" +
                            extra_param + R"( %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr(opcode + " reached non-composite type while indexes "
                                 "still remain to be traversed"));
}

TEST_P(ValidateMemoryUntypedAccessChain, TooManyIndices) {
  const std::string opcode = GetParam();
  const bool ptr = opcode == "OpUntypedPtrAccessChainKHR" ||
                   opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  const std::string extra_param = ptr ? "%int_0" : "";

  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpName %int_0 "int_0"
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%ptr_wg = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_wg StorageBuffer
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = )" + opcode + R"( %ptr %block %var )" +
                            extra_param + R"( %int_0 %int_0
OpReturn
OpFunctionEnd
)";

  getValidatorOptions()->relax_logical_pointer =
      opcode == "OpUntypedInBoundsPtrAccessChainKHR";
  CompileSuccessfully(spirv);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions());
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr(opcode + " reached non-composite type while indexes "
                                 "still remain to be traversed"));
}

INSTANTIATE_TEST_SUITE_P(
    ValidateUntypedAccessChains, ValidateMemoryUntypedAccessChain,
    Values("OpUntypedAccessChainKHR", "OpUntypedInBoundsAccessChainKHR",
           "OpUntypedPtrAccessChainKHR", "OpUntypedInBoundsPtrAccessChainKHR"));

TEST_F(ValidateMemory, LoadUntypedPointerGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%float = OpTypeFloat 32
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%load = OpLoad %float %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_F(ValidateMemory, StoreUntypedPointerGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%float = OpTypeFloat 32
%float_0 = OpConstant %float 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpStore %var %float_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_F(ValidateMemory, CopyMemoryUntypedPointerSourceGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var1 %var2
OpName %var1 "var1"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var1 = OpUntypedVariableKHR %ptr Workgroup %struct
%ptr_wg = OpTypePointer Workgroup %int
%var2 = OpVariable %ptr_wg Workgroup
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %var2 %var1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_F(ValidateMemory, CopyMemoryUntypedPointerTargetGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var1 %var2
OpName %var1 "var1"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var1 = OpUntypedVariableKHR %ptr Workgroup %struct
%ptr_wg = OpTypePointer Workgroup %int
%var2 = OpVariable %ptr_wg Workgroup
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %var1 %var2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_F(ValidateMemory, CopyMemoryUntypedPointerTargetAndSourceBad) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var1 %var2
OpName %var1 "var1"
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var1 = OpUntypedVariableKHR %ptr Workgroup %struct
%var2 = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemory %var1 %var2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("One of Source or Target must be a typed pointer"));
}

TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %v1 %v2
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%v1 = OpUntypedVariableKHR %ptr Workgroup %struct
%v2 = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemorySized %v2 %v1 %int_4
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersSizeBad1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability StorageBuffer16BitAccess
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var_wg %var_ssbo
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%short = OpTypeInt 16 0
%int_2 = OpConstant %int 2
%struct = OpTypeStruct %int
%ptr_ssbo = OpTypeUntypedPointerKHR StorageBuffer
%ptr_wg = OpTypeUntypedPointerKHR Workgroup
%var_ssbo = OpUntypedVariableKHR %ptr_ssbo StorageBuffer %struct
%var_wg = OpUntypedVariableKHR %ptr_wg Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemorySized %var_ssbo %var_wg %int_2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("Size must be a multiple of 4"));
}

TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersSizeBad2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability StorageBuffer16BitAccess
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var_ssbo %var_wg
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%short = OpTypeInt 16 0
%int_2 = OpConstant %int 2
%struct = OpTypeStruct %int
%ptr_ssbo = OpTypeUntypedPointerKHR StorageBuffer
%ptr_wg = OpTypeUntypedPointerKHR Workgroup
%var_ssbo = OpUntypedVariableKHR %ptr_ssbo StorageBuffer %struct
%var_wg = OpUntypedVariableKHR %ptr_wg Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemorySized %var_wg %var_ssbo %int_2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("Size must be a multiple of 4"));
}

TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersSizeBad3) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Int16
OpCapability UntypedPointersKHR
OpCapability StorageBuffer8BitAccess
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_8bit_storage"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var_ssbo %var_wg
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%short = OpTypeInt 16 0
%int_1 = OpConstant %int 1
%struct = OpTypeStruct %int
%ptr_ssbo = OpTypeUntypedPointerKHR StorageBuffer
%ptr_wg = OpTypeUntypedPointerKHR Workgroup
%var_ssbo = OpUntypedVariableKHR %ptr_ssbo StorageBuffer %struct
%var_wg = OpUntypedVariableKHR %ptr_wg Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemorySized %var_ssbo %var_wg %int_1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("Size must be a multiple of 2"));
}

TEST_F(ValidateMemory, CopyMemorySizedUntypedPointersSizeBad4) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Int16
OpCapability UntypedPointersKHR
OpCapability StorageBuffer8BitAccess
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_8bit_storage"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var_ssbo %var_wg
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%short = OpTypeInt 16 0
%int_1 = OpConstant %int 1
%struct = OpTypeStruct %int
%ptr_ssbo = OpTypeUntypedPointerKHR StorageBuffer
%ptr_wg = OpTypeUntypedPointerKHR Workgroup
%var_ssbo = OpUntypedVariableKHR %ptr_ssbo StorageBuffer %struct
%var_wg = OpUntypedVariableKHR %ptr_wg Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpCopyMemorySized %var_wg %var_ssbo %int_1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("Size must be a multiple of 2"));
}

TEST_F(ValidateMemory, PtrEqualUntypedPointersGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %v1 %v2
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%v1 = OpUntypedVariableKHR %ptr Workgroup %struct
%v2 = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%res = OpPtrEqual %bool %v1 %v2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_F(ValidateMemory, PtrNotEqualUntypedPointersGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %v1 %v2
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%bool = OpTypeBool
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%v1 = OpUntypedVariableKHR %ptr Workgroup %struct
%v2 = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%res = OpPtrNotEqual %bool %v1 %v2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_F(ValidateMemory, PtrDiffUntypedPointersGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %v1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%v1 = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%res = OpPtrDiff %int %v1 %v1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_4));
}

TEST_F(ValidateMemory, UntypedVariableVulkanPushConstantGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR PushConstant
%var = OpUntypedVariableKHR %ptr PushConstant %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}

TEST_F(ValidateMemory, UntypedVariableVulkanStorageBufferGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}

TEST_F(ValidateMemory, UntypedVariableVulkanUniformGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Uniform
%var = OpUntypedVariableKHR %ptr Uniform %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_0);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}

TEST_F(ValidateMemory, UntypedVariableVulkanWorkgroupGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %struct Block
OpMemberDecorate %struct 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%struct = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr Workgroup %struct
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
}

TEST_F(ValidateMemory, UntypedPointerAsVariableType) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability Linkage
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
%void = OpTypeVoid
%float = OpTypeFloat 32
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%priv_ptr = OpTypePointer Private %ptr
%var = OpVariable %priv_ptr Private
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, UntypedArrayLengthGood) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%array = OpTypeRuntimeArray %int
%block = OpTypeStruct %array
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%length = OpUntypedArrayLengthKHR %int %block %var 0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, UntypedArrayLengthBadResultType) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%float = OpTypeFloat 32
%array = OpTypeRuntimeArray %int
%block = OpTypeStruct %array
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%length = OpUntypedArrayLengthKHR %float %block %var 0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("must be OpTypeInt with width 32 or 64 and signedness 0"));
}

TEST_F(ValidateMemory, UntypedArrayLengthBadPointer) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%array = OpTypeRuntimeArray %int
%block = OpTypeStruct %array
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%typed_ptr = OpTypePointer StorageBuffer %block
%var = OpVariable %typed_ptr StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%length = OpUntypedArrayLengthKHR %int %block %var 0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Pointer must be an untyped pointer"));
}

TEST_F(ValidateMemory, UntypedArrayLengtBadStruct) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%array = OpTypeRuntimeArray %int
%block = OpTypeStruct %array
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%length = OpUntypedArrayLengthKHR %int %int %var 0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("to an OpTypeStruct"));
}

TEST_F(ValidateMemory, UntypedArrayLengthLastMemberNotArray) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%array = OpTypeRuntimeArray %int
%block = OpTypeStruct %int
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%length = OpUntypedArrayLengthKHR %int %block %var 0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("must be an OpTypeRuntimeArray"));
}

TEST_F(ValidateMemory, UntypedArrayLengthBadIndex) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%array = OpTypeRuntimeArray %int
%block = OpTypeStruct %array
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%length = OpUntypedArrayLengthKHR %int %block %var 1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("must be the last member of the struct"));
}

TEST_F(ValidateMemory, UntypedCooperativeMatrixLoad) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability CooperativeMatrixKHR
OpCapability VulkanMemoryModel
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_cooperative_matrix"
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%untyped = OpTypeUntypedPointerKHR StorageBuffer
%float = OpTypeFloat 32
%array = OpTypeRuntimeArray %float
%block = OpTypeStruct %array
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%subgroup = OpConstant %int 3
%rows = OpSpecConstant %int 1
%cols = OpSpecConstant %int 1
%matrix_a = OpConstant %int 1
%stride = OpConstant %int 42
%matrix = OpTypeCooperativeMatrixKHR %float %subgroup %rows %cols %matrix_a
%var = OpUntypedVariableKHR %untyped StorageBuffer %block
%main = OpFunction %void None %void_fn
%entry = OpLabel
%ld = OpCooperativeMatrixLoadKHR %matrix %var %int_0 %stride
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}

TEST_F(ValidateMemory, UntypedCooperativeMatrixLoad2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability CooperativeMatrixKHR
OpCapability VulkanMemoryModel
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_cooperative_matrix"
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%untyped = OpTypeUntypedPointerKHR StorageBuffer
%float = OpTypeFloat 32
%array = OpTypeRuntimeArray %float
%block = OpTypeStruct %array
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%subgroup = OpConstant %int 3
%rows = OpSpecConstant %int 1
%cols = OpSpecConstant %int 1
%matrix_a = OpConstant %int 1
%stride = OpConstant %int 42
%matrix = OpTypeCooperativeMatrixKHR %float %subgroup %rows %cols %matrix_a
%var = OpUntypedVariableKHR %untyped StorageBuffer %block
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpUntypedAccessChainKHR %untyped %block %var %int_0 %int_0
%ld = OpCooperativeMatrixLoadKHR %matrix %gep %int_0 %stride
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}

TEST_F(ValidateMemory, UntypedCooperativeMatrixStore) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability CooperativeMatrixKHR
OpCapability VulkanMemoryModel
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_cooperative_matrix"
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %main "main" %var1 %var2
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var1 DescriptorSet 0
OpDecorate %var1 Binding 0
OpDecorate %var2 DescriptorSet 0
OpDecorate %var2 Binding 1
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%untyped = OpTypeUntypedPointerKHR StorageBuffer
%float = OpTypeFloat 32
%array = OpTypeRuntimeArray %float
%block = OpTypeStruct %array
%ptr = OpTypePointer StorageBuffer %block
%ptr_float = OpTypePointer StorageBuffer %float
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%subgroup = OpConstant %int 3
%rows = OpSpecConstant %int 1
%cols = OpSpecConstant %int 1
%matrix_a = OpConstant %int 1
%stride = OpConstant %int 42
%matrix = OpTypeCooperativeMatrixKHR %float %subgroup %rows %cols %matrix_a
%var1 = OpVariable %ptr StorageBuffer
%var2 = OpUntypedVariableKHR %untyped StorageBuffer %block
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpAccessChain %ptr_float %var1 %int_0 %int_0
%ld = OpCooperativeMatrixLoadKHR %matrix %gep %int_0 %stride
OpCooperativeMatrixStoreKHR %var2 %ld %int_0 %stride
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}

TEST_F(ValidateMemory, UntypedCooperativeMatrixStore2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability CooperativeMatrixKHR
OpCapability VulkanMemoryModel
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_cooperative_matrix"
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %main "main" %var1 %var2
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var1 DescriptorSet 0
OpDecorate %var1 Binding 0
OpDecorate %var2 DescriptorSet 0
OpDecorate %var2 Binding 1
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
OpDecorate %array ArrayStride 4
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%untyped = OpTypeUntypedPointerKHR StorageBuffer
%float = OpTypeFloat 32
%array = OpTypeRuntimeArray %float
%block = OpTypeStruct %array
%ptr = OpTypePointer StorageBuffer %block
%ptr_float = OpTypePointer StorageBuffer %float
%int = OpTypeInt 32 0
%int_0 = OpConstant %int 0
%subgroup = OpConstant %int 3
%rows = OpSpecConstant %int 1
%cols = OpSpecConstant %int 1
%matrix_a = OpConstant %int 1
%stride = OpConstant %int 42
%matrix = OpTypeCooperativeMatrixKHR %float %subgroup %rows %cols %matrix_a
%var1 = OpVariable %ptr StorageBuffer
%var2 = OpUntypedVariableKHR %untyped StorageBuffer %block
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpAccessChain %ptr_float %var1 %int_0 %int_0
%ld = OpCooperativeMatrixLoadKHR %matrix %gep %int_0 %stride
%gep2 = OpUntypedAccessChainKHR %untyped %block %var2 %int_0 %int_0
OpCooperativeMatrixStoreKHR %gep2 %ld %int_0 %stride
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_3);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}

TEST_F(ValidateMemory, PtrAccessChainElementNotInteger) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %ptr_int ArrayStride 4
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%int_0 = OpConstant %int 0
%float = OpTypeFloat 32
%float_0 = OpConstant %float 0
%array = OpTypeArray %int %int_4
%ptr_array = OpTypePointer Workgroup %array
%ptr_int = OpTypePointer Workgroup %int
%var = OpVariable %ptr_array Workgroup
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpAccessChain %ptr_int %var %int_0
%ptr_gep = OpPtrAccessChain %ptr_int %gep %float_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("Element must be an integer"));
}

TEST_F(ValidateMemory, PtrAccessChainElementNotIntegerUntyped) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpCapability WorkgroupMemoryExplicitLayoutKHR
OpExtension "SPV_KHR_untyped_pointers"
OpExtension "SPV_KHR_workgroup_memory_explicit_layout"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %var
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %ptr_int ArrayStride 4
OpDecorate %array ArrayStride 4
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%int_0 = OpConstant %int 0
%float = OpTypeFloat 32
%float_0 = OpConstant %float 0
%array = OpTypeArray %int %int_4
%block = OpTypeStruct %array
%ptr_block = OpTypeUntypedPointerKHR Workgroup
%ptr_int = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr_block Workgroup %block
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpUntypedAccessChainKHR %ptr_int %block %var %int_0 %int_0
%ptr_gep = OpUntypedPtrAccessChainKHR %ptr_int %int %gep %float_0
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_2);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_2));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("Element must be an integer"));
}

TEST_F(ValidateMemory, PtrAccessChainElementBlockArrayNonZeroConstant) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%int_1 = OpConstant %int 1
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%array = OpTypeArray %block %int_4
%ptr_array = OpTypePointer StorageBuffer %array
%ptr_block = OpTypePointer StorageBuffer %block
%var = OpVariable %ptr_array StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpAccessChain %ptr_block %var %int_0
%ptr_gep = OpPtrAccessChain %ptr_block %gep %int_1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Element must be 0 if the interpretation type is a "
                        "Block- or BufferBlock-decorated structure"));
}

TEST_F(ValidateMemory, PtrAccessChainElementBlockArrayNonZeroConstantUntyped) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_4 = OpConstant %int 4
%int_1 = OpConstant %int 1
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%array = OpTypeArray %block %int_4
%ptr_array = OpTypeUntypedPointerKHR StorageBuffer
%ptr_block = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr_array StorageBuffer %array
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpUntypedAccessChainKHR %ptr_block %array %var %int_0
%ptr_gep = OpUntypedPtrAccessChainKHR %ptr_block %block %gep %int_1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Element must be 0 if the interpretation type is a "
                        "Block- or BufferBlock-decorated structure"));
}

TEST_F(ValidateMemory, UntypedAccessChainBlockArrayMismatch1) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_1 = OpConstant %int 1
%int_4 = OpConstant %int 4
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%array1 = OpTypeArray %block %int_4
%array2 = OpTypeArray %block %int_4
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %array1
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpUntypedAccessChainKHR %ptr %array2 %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("If Base or Base Type is a Block or BufferBlock array, "
                        "the other must also be the same array"));
}

TEST_F(ValidateMemory, UntypedAccessChainBlockArrayMismatch2) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_1 = OpConstant %int 1
%int_4 = OpConstant %int 4
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%array1 = OpTypeArray %block %int_4
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%var = OpUntypedVariableKHR %ptr StorageBuffer %array1
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpUntypedAccessChainKHR %ptr %block %var
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Both Base Type and Base must be Block or BufferBlock "
                        "arrays or neither can be"));
}

TEST_F(ValidateMemory, UntypedAccessChainBlockArrayMismatch3) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_1 = OpConstant %int 1
%int_4 = OpConstant %int 4
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%array1 = OpTypeArray %block %int_4
%array2 = OpTypeArray %block %int_4
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%ptr_block_array = OpTypePointer StorageBuffer %array1
%var = OpVariable %ptr_block_array StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%copy1 = OpCopyObject %ptr_block_array %var
%copy2 = OpCopyObject %ptr_block_array %copy1
%gep = OpUntypedAccessChainKHR %ptr %array2 %copy2
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("If Base or Base Type is a Block or BufferBlock array, "
                        "the other must also be the same array"));
}

TEST_F(ValidateMemory, UntypedAccessChainBlockArrayMismatch4) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
OpDecorate %var DescriptorSet 0
OpDecorate %var Binding 0
OpDecorate %block Block
OpMemberDecorate %block 0 Offset 0
%void = OpTypeVoid
%int = OpTypeInt 32 0
%int_1 = OpConstant %int 1
%int_4 = OpConstant %int 4
%int_0 = OpConstant %int 0
%block = OpTypeStruct %int
%array1 = OpTypeArray %block %int_4
%ptr = OpTypeUntypedPointerKHR StorageBuffer
%ptr_block_array = OpTypePointer StorageBuffer %array1
%var = OpVariable %ptr_block_array StorageBuffer
%void_fn = OpTypeFunction %void
%main = OpFunction %void None %void_fn
%entry = OpLabel
%copy = OpCopyObject %ptr_block_array %var
%gep = OpUntypedAccessChainKHR %ptr %block %copy
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_VULKAN_1_1);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_VULKAN_1_1));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Both Base Type and Base must be Block or BufferBlock "
                        "arrays or neither can be"));
}

TEST_F(ValidateMemory, VariableFunctionPointer) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%ptr = OpTypePointer Function %int
%ptr_ptr = OpTypePointer Function %ptr
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_ptr Function
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("In Logical addressing, variables can only allocate a pointer "
                "to the StorageBuffer or Workgroup storage classes"));
}

TEST_F(ValidateMemory, VariableFunctionPointerUntyped) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%ptr = OpTypeUntypedPointerKHR Function
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpUntypedVariableKHR %ptr Function %ptr
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("In Logical addressing, variables can only allocate a pointer "
                "to the StorageBuffer or Workgroup storage classes"));
}

TEST_F(ValidateMemory, VariableStorageBufferPointer) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%ptr = OpTypePointer StorageBuffer %int
%ptr_ptr = OpTypePointer Function %ptr
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_ptr Function
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "In Logical addressing, variables can only allocate a storage "
          "buffer pointer if the VariablePointersStorageBuffer capability "
          "is declared"));
}

TEST_F(ValidateMemory, VariableStorageBufferPointerUntyped) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%ptr_s = OpTypeUntypedPointerKHR StorageBuffer
%ptr_f = OpTypeUntypedPointerKHR Function
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpUntypedVariableKHR %ptr_f Function %ptr_s
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "In Logical addressing, variables can only allocate a storage "
          "buffer pointer if the VariablePointersStorageBuffer capability "
          "is declared"));
}

TEST_F(ValidateMemory, VariableWorkgroupPointer) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%ptr = OpTypePointer Workgroup %int
%ptr_ptr = OpTypePointer Function %ptr
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpVariable %ptr_ptr Function
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "In Logical addressing, variables can only allocate a workgroup "
          "pointer if the VariablePointers capability is declared"));
}

TEST_F(ValidateMemory, VariableWorkgroupPointerUntyped) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%ptr_w = OpTypeUntypedPointerKHR Workgroup
%ptr_f = OpTypeUntypedPointerKHR Function
%main = OpFunction %void None %void_fn
%entry = OpLabel
%var = OpUntypedVariableKHR %ptr_f Function %ptr_w
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "In Logical addressing, variables can only allocate a workgroup "
          "pointer if the VariablePointers capability is declared"));
}

TEST_F(ValidateMemory, VariablePointerBadStorageClass) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability VariablePointers
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%ptr = OpTypePointer Workgroup %int
%ptr_ptr = OpTypePointer Workgroup %ptr
%var = OpVariable %ptr_ptr Workgroup
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "In Logical addressing with variable pointers, variables that "
          "allocate pointers must be in Function or Private storage classes"));
}

TEST_F(ValidateMemory, VariablePointerBadStorageClassUntyped) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability UntypedPointersKHR
OpCapability VariablePointers
OpExtension "SPV_KHR_untyped_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 0
%ptr_w = OpTypeUntypedPointerKHR Workgroup
%var = OpUntypedVariableKHR %ptr_w Workgroup %ptr_w
%main = OpFunction %void None %void_fn
%entry = OpLabel
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "In Logical addressing with variable pointers, variables that "
          "allocate pointers must be in Function or Private storage classes"));
}

TEST_F(ValidateMemory, AccessChainNegativeSignedIndex) {
  const std::string spirv = R"(
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%int = OpTypeInt 32 1
%int_n1 = OpConstant %int -1
%int_4 = OpConstant %int 4
%array = OpTypeArray %int %int_4
%ptr_array = OpTypePointer Workgroup %array
%ptr_int = OpTypePointer Workgroup %int
%var = OpVariable %ptr_array Workgroup
%main = OpFunction %void None %void_fn
%entry = OpLabel
%gep = OpAccessChain %ptr_int %var %int_n1
OpReturn
OpFunctionEnd
)";

  CompileSuccessfully(spirv, SPV_ENV_UNIVERSAL_1_3);
  EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Index at word 4 may not have a negative value"));
}

std::string GenCoopMat2Shader(const std::string& extra_types,
                              const std::string& main_body,
                              const std::string& after_main = "",
                              const std::string& extra_decorations = "") {
  const std::string prefix = R"(
OpCapability Shader
OpCapability Float16
OpCapability PhysicalStorageBufferAddresses
OpCapability VulkanMemoryModel
OpCapability CooperativeMatrixKHR
OpCapability TensorAddressingNV
OpCapability CooperativeMatrixTensorAddressingNV
OpCapability CooperativeMatrixBlockLoadsNV
OpExtension "SPV_KHR_physical_storage_buffer"
OpExtension "SPV_KHR_storage_buffer_storage_class"
OpExtension "SPV_NV_tensor_addressing"
OpExtension "SPV_NV_cooperative_matrix2"
OpExtension "SPV_KHR_cooperative_matrix"
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical VulkanKHR
OpEntryPoint GLCompute %main "main"
OpExecutionMode %main LocalSize 1 1 1

OpDecorate %f16_arr ArrayStride 2
OpDecorate %46 Block
OpMemberDecorate %46 0 Offset 0
OpDecorate %48 Binding 0
OpDecorate %48 DescriptorSet 0
OpDecorate %psb Restrict
)" + extra_decorations + R"(
%void = OpTypeVoid
%bool = OpTypeBool
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%f32 = OpTypeFloat 32
%u32 = OpTypeInt 32 0
%s32 = OpTypeInt 32 1

%s32_0 = OpConstant %s32 0
%f16_0 = OpConstant %f16 0
%u32_2 = OpConstant %u32 2
%u32_8 = OpConstant %u32 8
%use_A = OpConstant %u32 0
%workgroup = OpConstant %u32 2
%subgroup = OpConstant %u32 3

%f16_arr = OpTypeRuntimeArray %f16
%46 = OpTypeStruct %f16_arr
%47 = OpTypePointer StorageBuffer %46
%48 = OpVariable %47 StorageBuffer
%51 = OpTypePointer StorageBuffer %f16_arr
%psbptr = OpTypePointer PhysicalStorageBuffer %f16_arr

%f16mat = OpTypeCooperativeMatrixKHR %f16 %workgroup %u32_8 %u32_8 %use_A
%f32mat = OpTypeCooperativeMatrixKHR %f32 %subgroup %u32_8 %u32_8 %use_A

%arr2 = OpTypeArray %u32 %u32_2
%functy = OpTypeFunction %f16 %psbptr %arr2 %arr2
)";

  const std::string decode_func =
      R"(
%decodefunc = OpFunction %f16 None %functy
%psb = OpFunctionParameter %psbptr
%c0 =  OpFunctionParameter %arr2
%c1 =  OpFunctionParameter %arr2
%entry2 = OpLabel
OpReturnValue %f16_0
OpFunctionEnd
)";

  const std::string func_begin =
      R"(
%main = OpFunction %void None %func
%main_entry = OpLabel

%array_ptr = OpAccessChain %51 %48 %s32_0
)";

  const std::string suffix =
      R"(
OpReturn
OpFunctionEnd)";

  return prefix + extra_types + func_begin + main_body + suffix + decode_func +
         after_main;
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutAndViewSuccess) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 2
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %layout = OpTypeTensorLayoutNV %dim %clamp
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      )",
      R"(
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutInvalidDimFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 6
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be between 1 and 5"));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutInvalidClampFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 6
      %dim = OpConstant %u32 2
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("must be a valid TensorClampMode"));
}

TEST_F(ValidateMemory, CoopMat2TensorViewInvalidDimFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %dim = OpConstant %u32 6
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      )",
      R"(
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(), HasSubstr("must be between 1 and 5"));
}

TEST_F(ValidateMemory, CoopMat2TensorViewInvalidPermutationFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %dim = OpConstant %u32 3
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1 %p1
      )",
      R"(
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Permutation values don't form a valid permutation"));
}

TEST_F(ValidateMemory, CoopMat2TensorViewInvalidPermutation2Fail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %dim = OpConstant %u32 3
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      )",
      R"(
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("Incorrect number of permutation values."));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutBlockSizePass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSetBlockSizeNV %layout %tl %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutBlockSizeFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSetBlockSizeNV %layout %tl %b %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("unexpected number of operands"));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutDimensionPass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSetDimensionNV %layout %tl %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutDimensionFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSetDimensionNV %layout %tl %b %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("unexpected number of operands"));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutStridePass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSetStrideNV %layout %tl %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutStrideFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSetStrideNV %layout %tl %b %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("unexpected number of operands"));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutSlicePass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSliceNV %layout %tl %b %b %b %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutSliceFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSliceNV %layout %tl %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("unexpected number of operands"));
}

TEST_F(ValidateMemory, CoopMat2TensorLayoutSetClampValuePass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 3
      %b = OpConstant %u32 1
      %layout = OpTypeTensorLayoutNV %dim %clamp
      )",
      R"(
      %tl = OpCreateTensorLayoutNV %layout
      %tl2 = OpTensorLayoutSetClampValueNV %layout %tl %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2TensorViewDimensionPass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %dim = OpConstant %u32 3
      %hasdim = OpConstantFalse %bool
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %p2 = OpConstant %u32 2
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1 %p2
      %b = OpConstant %u32 1
      )",
      R"(
      %tv = OpCreateTensorViewNV %view
      %tv2 = OpTensorViewSetDimensionNV %view %tv %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2TensorViewDimensionFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %dim = OpConstant %u32 3
      %hasdim = OpConstantFalse %bool
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %p2 = OpConstant %u32 2
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1 %p2
      %b = OpConstant %u32 1
      )",
      R"(
      %tv = OpCreateTensorViewNV %view
      %tv2 = OpTensorViewSetDimensionNV %view %tv %b %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("unexpected number of operands"));
}

TEST_F(ValidateMemory, CoopMat2TensorViewStridePass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %dim = OpConstant %u32 3
      %hasdim = OpConstantFalse %bool
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %p2 = OpConstant %u32 2
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1 %p2
      %b = OpConstant %u32 1
      )",
      R"(
      %tv = OpCreateTensorViewNV %view
      %tv2 = OpTensorViewSetStrideNV %view %tv %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2TensorViewStrideFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %dim = OpConstant %u32 3
      %hasdim = OpConstantFalse %bool
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %p2 = OpConstant %u32 2
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1 %p2
      %b = OpConstant %u32 1
      )",
      R"(
      %tv = OpCreateTensorViewNV %view
      %tv2 = OpTensorViewSetStrideNV %view %tv %b %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("unexpected number of operands"));
}

TEST_F(ValidateMemory, CoopMat2TensorViewClipPass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %dim = OpConstant %u32 3
      %hasdim = OpConstantFalse %bool
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %p2 = OpConstant %u32 2
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1 %p2
      %b = OpConstant %u32 1
      )",
      R"(
      %tv = OpCreateTensorViewNV %view
      %tv2 = OpTensorViewSetClipNV %view %tv %b %b %b %b
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2LoadStoreTensorPass) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 2
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %layout = OpTypeTensorLayoutNV %dim %clamp
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      )",
      R"(
      %mat = OpUndef %f16mat
      %tl = OpCreateTensorLayoutNV %layout
      %tv = OpCreateTensorViewNV %view
      %mat2 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl None None
      %mat3 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl Aligned 4 None
      %mat4 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl None TensorView %tv
      %mat5 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl None DecodeFunc %decodefunc
      %mat6 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl None TensorView|DecodeFunc %tv %decodefunc
      %mat7 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl Aligned 4 TensorView|DecodeFunc %tv %decodefunc
      OpCooperativeMatrixStoreTensorNV %array_ptr %mat %tl None None
      OpCooperativeMatrixStoreTensorNV %array_ptr %mat %tl Aligned 4 None
      OpCooperativeMatrixStoreTensorNV %array_ptr %mat %tl None TensorView %tv
      OpCooperativeMatrixStoreTensorNV %array_ptr %mat %tl Aligned 4 TensorView %tv
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
}

TEST_F(ValidateMemory, CoopMat2LoadTensorWrongLayoutTypeFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 2
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %layout = OpTypeTensorLayoutNV %dim %clamp
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      )",
      R"(
      %mat = OpUndef %f16mat
      %tl = OpCreateTensorLayoutNV %layout
      %tv = OpCreateTensorViewNV %view
      %mat2 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tv None None
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("does not have a tensor layout type"));
}

TEST_F(ValidateMemory, CoopMat2LoadTensorWrongObjectTypeFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 2
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %layout = OpTypeTensorLayoutNV %dim %clamp
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      )",
      R"(
      %mat = OpUndef %f32mat
      %tl = OpCreateTensorLayoutNV %layout
      %tv = OpCreateTensorViewNV %view
      %mat2 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl None None
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("type does not match Result Type"));
}

TEST_F(ValidateMemory, CoopMat2LoadTensorDecodeFuncTypeFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 2
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %layout = OpTypeTensorLayoutNV %dim %clamp
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      )",
      R"(
      %mat = OpUndef %f32mat
      %tl = OpCreateTensorLayoutNV %layout
      %tv = OpCreateTensorViewNV %view
      %mat2 = OpCooperativeMatrixLoadTensorNV %f32mat %array_ptr %mat %tl None DecodeFunc %decodefunc
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("return type must match matrix component type"));
}

TEST_F(ValidateMemory, CoopMat2LoadTensorDecodeFuncArrayTypeFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 2
      %u32_3 = OpConstant %u32 3
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %layout = OpTypeTensorLayoutNV %dim %clamp
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      %arr3 = OpTypeArray %u32 %u32_3
      %functy2 = OpTypeFunction %f16 %psbptr %arr3 %arr3
      )",
      R"(
      %mat = OpUndef %f16mat
      %tl = OpCreateTensorLayoutNV %layout
      %tv = OpCreateTensorViewNV %view
      %mat2 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl None DecodeFunc %decodefunc2
      )",
      R"(
      %decodefunc2 = OpFunction %f16 None %functy2
      %psb2 = OpFunctionParameter %psbptr
      %c02 =  OpFunctionParameter %arr3
      %c12 =  OpFunctionParameter %arr3
      %entry3 = OpLabel
      OpReturnValue %f16_0
      OpFunctionEnd
      )",
      R"(
      OpDecorate %psb2 Restrict
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("dimension equal to the tensor dimension"));
}

TEST_F(ValidateMemory, CoopMat2LoadTensorDecodeFuncPointerTypeFail) {
  std::string spirv = GenCoopMat2Shader(
      R"(
      %clamp = OpConstant %u32 0
      %dim = OpConstant %u32 2
      %p0 = OpConstant %u32 0
      %p1 = OpConstant %u32 1
      %hasdim = OpConstantFalse %bool
      %layout = OpTypeTensorLayoutNV %dim %clamp
      %view = OpTypeTensorViewNV %dim %hasdim %p0 %p1
      %sbptr = OpTypePointer StorageBuffer %f16_arr
      %functy2 = OpTypeFunction %f16 %sbptr %arr2 %arr2
      )",
      R"(
      %mat = OpUndef %f16mat
      %tl = OpCreateTensorLayoutNV %layout
      %tv = OpCreateTensorViewNV %view
      %mat2 = OpCooperativeMatrixLoadTensorNV %f16mat %array_ptr %mat %tl None DecodeFunc %decodefunc2
      )",
      R"(
      %decodefunc2 = OpFunction %f16 None %functy2
      %sb = OpFunctionParameter %sbptr
      %c02 =  OpFunctionParameter %arr2
      %c12 =  OpFunctionParameter %arr2
      %entry3 = OpLabel
      OpReturnValue %f16_0
      OpFunctionEnd
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("first parameter must be pointer to PhysicalStorageBuffer"));
}

TEST_F(ValidateMemory, PtrAccessChainNodePayloadArray) {
  const std::string spirv = R"(
OpCapability Shader
OpCapability ShaderEnqueueAMDX
OpExtension "SPV_AMDX_shader_enqueue"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main" %input
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%node0 = OpConstantStringAMDX "node0"
%node1 = OpConstantStringAMDX "node1"
%node2 = OpConstantStringAMDX "node2"
%S = OpTypeStruct %uint
%_payloadarr_S = OpTypeNodePayloadArrayAMDX %S
%_ptr_NodePayloadAMDX__payloadarr_S = OpTypePointer NodePayloadAMDX %_payloadarr_S
%_ptr_NodePayloadAMDX_uint = OpTypePointer NodePayloadAMDX %uint
%void = OpTypeVoid
%void_fn = OpTypeFunction %void
%input = OpVariable %_ptr_NodePayloadAMDX__payloadarr_S NodePayloadAMDX
%main = OpFunction %void None %void_fn
%entry = OpLabel
%x = OpAccessChain %_ptr_NodePayloadAMDX_uint %input %uint_0 %uint_0
OpReturn
OpFunctionEnd
)";

  spv_target_env env = SPV_ENV_UNIVERSAL_1_4;
  CompileSuccessfully(spirv, env);
  EXPECT_THAT(SPV_SUCCESS, ValidateInstructions(env));
}

std::string GenCoopVecLoadStoreShader(const std::string& storeMemoryAccess,
                                      const std::string& loadMemoryAccess) {
  std::string s = R"(
OpCapability Shader
OpCapability Float16
OpCapability StorageBuffer16BitAccess
OpCapability VulkanMemoryModel
OpCapability CooperativeVectorNV
OpCapability ReplicatedCompositesEXT
OpExtension "SPV_EXT_replicated_composites"
OpExtension "SPV_KHR_vulkan_memory_model"
OpExtension "SPV_NV_cooperative_vector"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %4 "main" %48 %73
OpExecutionMode %4 LocalSize 1 1 1

OpDecorate %45 ArrayStride 2
OpDecorate %46 Block
OpMemberDecorate %46 0 Offset 0
OpDecorate %48 Binding 0
OpDecorate %48 DescriptorSet 0

%2 = OpTypeVoid
%3 = OpTypeFunction %2
%6 = OpTypeInt 32 0
%49 = OpTypeInt 32 1
%41 = OpTypeFloat 16

%14 = OpConstant %6 1
%50 = OpConstant %49 0
%82 = OpConstant %6 5

%42 = OpTypeCooperativeVectorNV %41 %14
%43 = OpTypePointer Function %42

%45 = OpTypeRuntimeArray %41
%46 = OpTypeStruct %45
%47 = OpTypePointer StorageBuffer %46
%48 = OpVariable %47 StorageBuffer
%51 = OpTypePointer StorageBuffer %45

%57 = OpTypePointer Private %42
%73 = OpVariable %57 Private

%4 = OpFunction %2 None %3
%5 = OpLabel
%52 = OpAccessChain %51 %48 %50
%56 = OpCooperativeVectorLoadNV %42 %52 %50 )" +
                  loadMemoryAccess + R"( %82
%77 = OpLoad %42 %73
OpCooperativeVectorStoreNV %52 %50 %77 )" +
                  storeMemoryAccess + R"( %82
OpReturn
OpFunctionEnd
)";

  return s;
}

TEST_F(ValidateMemory, CoopVecLoadStoreSuccess) {
  std::string spirv =
      GenCoopVecLoadStoreShader("MakePointerAvailableKHR|NonPrivatePointerKHR",
                                "MakePointerVisibleKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
}

TEST_F(ValidateMemory, CoopVecStoreMemoryAccessFail) {
  std::string spirv =
      GenCoopVecLoadStoreShader("MakePointerVisibleKHR|NonPrivatePointerKHR",
                                "MakePointerVisibleKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MakePointerVisibleKHR cannot be used with OpStore"));
}

TEST_F(ValidateMemory, CoopVecLoadMemoryAccessFail) {
  std::string spirv =
      GenCoopVecLoadStoreShader("MakePointerAvailableKHR|NonPrivatePointerKHR",
                                "MakePointerAvailableKHR|NonPrivatePointerKHR");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("MakePointerAvailableKHR cannot be used with OpLoad"));
}

TEST_F(ValidateMemory, CoopVecInvalidStorageClassFail) {
  const std::string body = R"(
OpCapability Shader
OpCapability Float16
OpCapability CooperativeVectorNV
OpCapability ReplicatedCompositesEXT
OpExtension "SPV_NV_cooperative_vector"
OpExtension "SPV_EXT_replicated_composites"
OpExtension "SPV_KHR_vulkan_memory_model"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %main "main"
%void = OpTypeVoid
%func = OpTypeFunction %void
%f16 = OpTypeFloat 16
%u32 = OpTypeInt 32 0

%u32_8 = OpConstant %u32 8
%use_A = OpConstant %u32 0
%subgroup = OpConstant %u32 3

%f16vec = OpTypeCooperativeVectorNV %f16 %u32_8

%str = OpTypeStruct %f16vec
%str_ptr = OpTypePointer Workgroup %str
%sh = OpVariable %str_ptr Workgroup

%main = OpFunction %void None %func
%main_entry = OpLabel

OpReturn
OpFunctionEnd)";

  CompileSuccessfully(body.c_str(), SPV_ENV_UNIVERSAL_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr(
          "Cooperative vector types (or types containing them) can only be "
          "allocated in Function or Private storage classes or as function "
          "parameters"));
}

std::string GenCoopVecShader(const std::string& extra_types,
                             const std::string& main_body,
                             const std::string& execution_modes = "") {
  const std::string prefix =
      R"(
OpCapability Shader
OpCapability Float16
OpCapability Int64
OpCapability Int16
OpCapability StorageBuffer16BitAccess
OpCapability VulkanMemoryModel
OpCapability CooperativeVectorNV
OpCapability CooperativeVectorTrainingNV
OpCapability ReplicatedCompositesEXT
OpCapability Shader64BitIndexingEXT
OpExtension "SPV_EXT_replicated_composites"
OpExtension "SPV_KHR_vulkan_memory_model"
OpExtension "SPV_NV_cooperative_vector"
OpExtension "SPV_EXT_shader_64bit_indexing"
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical Vulkan
OpEntryPoint GLCompute %main "main" %48 %73
OpExecutionMode %main LocalSize 1 1 1

)" + execution_modes +
      R"(

OpDecorate %f16_arr ArrayStride 2
OpDecorate %46 Block
OpMemberDecorate %46 0 Offset 0
OpDecorate %48 Binding 0
OpDecorate %48 DescriptorSet 0

%void = OpTypeVoid
%func = OpTypeFunction %void
%u32 = OpTypeInt 32 0
%s32 = OpTypeInt 32 1
%f16 = OpTypeFloat 16
%bool = OpTypeBool

%false = OpConstantFalse %bool
%u32_4 = OpConstant %u32 4
%u32_8 = OpConstant %u32 8
%s32_0 = OpConstant %s32 0
%f16_0 = OpConstant %f16 0

%f16vec4 = OpTypeCooperativeVectorNV %f16 %u32_4
%f16vec8 = OpTypeCooperativeVectorNV %f16 %u32_8

%f16_arr = OpTypeRuntimeArray %f16
%46 = OpTypeStruct %f16_arr
%47 = OpTypePointer StorageBuffer %46
%48 = OpVariable %47 StorageBuffer
%51 = OpTypePointer StorageBuffer %f16_arr

%57 = OpTypePointer Private %f16vec4
%73 = OpVariable %57 Private
%u32ptr = OpTypePointer Function %u32

%input4 = OpConstantCompositeReplicateEXT %f16vec4 %f16_0
%input8 = OpConstantCompositeReplicateEXT %f16vec8 %f16_0
%interp = OpConstant %u32 0
%offset = OpConstant %u32 0

)";

  const std::string func_begin =
      R"(
%main = OpFunction %void None %func
%main_entry = OpLabel
%u32var = OpVariable %u32ptr Function
%array_ptr = OpAccessChain %51 %48 %s32_0
)";

  const std::string suffix =
      R"(
OpReturn
OpFunctionEnd)";

  return prefix + extra_types + func_begin + main_body + suffix;
}

TEST_F(ValidateMemory, CoopVecMatMulSuccess) {
  std::string spirv = GenCoopVecShader("",
                                       R"(
%result0 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input4 %interp %array_ptr %offset %interp %array_ptr %offset %interp %u32_4 %u32_4 %s32_0 %false
%result1 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input8 %interp %array_ptr %offset %interp %array_ptr %offset %interp %u32_4 %u32_8 %s32_0 %false
%result2 = OpCooperativeVectorMatrixMulAddNV %f16vec8 %input4 %interp %array_ptr %offset %interp %array_ptr %offset %interp %u32_8 %u32_4 %s32_0 %false
%result3 = OpCooperativeVectorMatrixMulNV %f16vec4 %input4 %interp %array_ptr %offset %interp %u32_4 %u32_4 %s32_0 %false
%result4 = OpCooperativeVectorMatrixMulNV %f16vec4 %input8 %interp %array_ptr %offset %interp %u32_4 %u32_8 %s32_0 %false
%result5 = OpCooperativeVectorMatrixMulNV %f16vec8 %input4 %interp %array_ptr %offset %interp %u32_8 %u32_4 %s32_0 %false

OpCooperativeVectorReduceSumAccumulateNV %array_ptr %offset %input4
OpCooperativeVectorOuterProductAccumulateNV %array_ptr %offset %input4 %input8 %interp %interp
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
}

TEST_F(ValidateMemory, CoopVecMatMulKMismatchFail) {
  std::string spirv = GenCoopVecShader(R"()",
                                       R"(
%result1 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input8 %interp %array_ptr %offset %interp %array_ptr %offset %interp %u32_4 %u32_4 %s32_0 %false
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorMatrixMulAddNV input number of "
                        "components 8 does not match K 4"));
}

TEST_F(ValidateMemory, CoopVecMatMulPackedKMismatchPass) {
  std::string spirv = GenCoopVecShader(
      R"(
%packed = OpConstant %u32 1000491001
      )",
      R"(
%result1 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input8 %packed %array_ptr %offset %interp %array_ptr %offset %interp %u32_4 %u32_4 %s32_0 %false
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
}

TEST_F(ValidateMemory, CoopVecMatMulMMismatchFail) {
  std::string spirv = GenCoopVecShader(R"()",
                                       R"(
%result1 = OpCooperativeVectorMatrixMulAddNV %f16vec8 %input8 %interp %array_ptr %offset %interp %array_ptr %offset %interp %u32_4 %u32_8 %s32_0 %false
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorMatrixMulAddNV result type number "
                        "of components 8 does not match M 4"));
}

TEST_F(ValidateMemory, CoopVecMatMulTransposeTypeFail) {
  std::string spirv = GenCoopVecShader(R"()",
                                       R"(
%result0 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input4 %interp %array_ptr %offset %interp %array_ptr %offset %interp %u32_4 %u32_4 %s32_0 %s32_0
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorMatrixMulAddNV Transpose <id> "
                        "'16[%int_0]' is not a scalar boolean"));
}

TEST_F(ValidateMemory, CoopVecMatMulInputInterpretationNotConstantFail) {
  std::string spirv = GenCoopVecShader(
      R"(
      )",
      R"(
%u32val = OpLoad %u32 %u32var
%result0 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input4 %u32val %array_ptr %offset %interp %array_ptr %offset %interp %u32_4 %u32_4 %s32_0 %false
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorMatrixMulAddNV InputInterpretation "
                        "<id> '31[%31]' is not a constant instruction"));
}

TEST_F(ValidateMemory, CoopVecMatMulMatrixInterpretationNotConstantFail) {
  std::string spirv = GenCoopVecShader(
      R"(
      )",
      R"(
%u32val = OpLoad %u32 %u32var
%result0 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input4 %interp %array_ptr %offset %u32val %array_ptr %offset %interp %u32_4 %u32_4 %s32_0 %false
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("OpCooperativeVectorMatrixMulAddNV MatrixInterpretation <id> "
                "'31[%31]' is not a constant instruction"));
}

TEST_F(ValidateMemory, CoopVecMatMulBiasInterpretationNotConstantFail) {
  std::string spirv = GenCoopVecShader(
      R"(
      )",
      R"(
%u32val = OpLoad %u32 %u32var
%result0 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input4 %interp %array_ptr %offset %interp %array_ptr %offset %u32val %u32_4 %u32_4 %s32_0 %false
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorMatrixMulAddNV BiasInterpretation "
                        "<id> '31[%31]' is not a constant instruction"));
}

TEST_F(ValidateMemory, CoopVecMatMulInputInterpretationNotInt32Fail) {
  std::string spirv = GenCoopVecShader(
      R"(
      )",
      R"(
%result0 = OpCooperativeVectorMatrixMulAddNV %f16vec4 %input4 %false %array_ptr %offset %interp %array_ptr %offset %interp %u32_4 %u32_4 %s32_0 %false
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorMatrixMulAddNV InputInterpretation "
                        "type <id> '12[%bool]' is not a 32 bit integer"));
}

TEST_F(ValidateMemory, CoopVecOuterProductABMismatchFail) {
  std::string spirv = GenCoopVecShader(
      R"(
%f32 = OpTypeFloat 32
%f32vec8 = OpTypeCooperativeVectorNV %f32 %u32_8
%f32_0 = OpConstant %f32 0
%input8f32 = OpConstantCompositeReplicateEXT %f32vec8 %f32_0
      )",
      R"(
OpCooperativeVectorOuterProductAccumulateNV %array_ptr %offset %input4 %input8f32 %interp %interp
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("OpCooperativeVectorOuterProductAccumulateNV A and B component "
                "types '11[%half]' and '28[%float]' do not match"));
}

TEST_F(ValidateMemory, CoopVecOuterProductIntOffsetFail) {
  std::string spirv = GenCoopVecShader(
      R"(
%u16 = OpTypeInt 16 0
%u16_0 = OpConstant %u16 0
      )",
      R"(
OpCooperativeVectorOuterProductAccumulateNV %array_ptr %u16_0 %input4 %input8 %interp %interp
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("OpCooperativeVectorOuterProductAccumulateNV Offset "
                "type <id> '28[%ushort]' is not a 32 or 64 bit integer"));
}

TEST_F(ValidateMemory, CoopVecOuterProductInt32MatrixStrideFail) {
  std::string spirv = GenCoopVecShader(
      R"(
%u64 = OpTypeInt 64 0
%u64_0 = OpConstant %u64 0
      )",
      R"(
OpCooperativeVectorOuterProductAccumulateNV %array_ptr %offset %input4 %input8 %interp %interp %u64_0
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(
      getDiagnosticString(),
      HasSubstr("OpCooperativeVectorOuterProductAccumulateNV MatrixStride type "
                "<id> '28[%ulong]' is not a 32 bit integer"));
}

TEST_F(ValidateMemory, CoopVecOuterProductVectorTypeFail) {
  std::string spirv = GenCoopVecShader(
      R"(
%f16v4 = OpTypeVector %f16 4
%f16c = OpConstantCompositeReplicateEXT %f16v4 %f16_0
      )",
      R"(
OpCooperativeVectorOuterProductAccumulateNV %array_ptr %offset %f16c %input8 %interp %interp
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorOuterProductAccumulateNV A type "
                        "<id> '28[%v4half]' is not a cooperative vector type"));
}

TEST_F(ValidateMemory, CoopVecReduceSumIntOffsetFail) {
  std::string spirv = GenCoopVecShader(
      R"(
%u16 = OpTypeInt 16 0
%u16_0 = OpConstant %u16 0
      )",
      R"(
OpCooperativeVectorReduceSumAccumulateNV %array_ptr %u16_0 %input4
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorReduceSumAccumulateNV Offset type "
                        "<id> '28[%ushort]' is not a 32 or 64 bit integer"));
}

TEST_F(ValidateMemory, CoopVecOuterProductInt64OffsetPass) {
  std::string spirv = GenCoopVecShader(
      R"(
%u64 = OpTypeInt 64 0
%u64_0 = OpConstant %u64 0
      )",
      R"(
OpCooperativeVectorOuterProductAccumulateNV %array_ptr %u64_0 %input4 %input8 %interp %interp
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
}

TEST_F(ValidateMemory, CoopVecReduceSumVectorTypeFail) {
  std::string spirv = GenCoopVecShader(
      R"(
%f16v4 = OpTypeVector %f16 4
%f16c = OpConstantCompositeReplicateEXT %f16v4 %f16_0
      )",
      R"(
OpCooperativeVectorReduceSumAccumulateNV %array_ptr %offset %f16c
      )");

  CompileSuccessfully(spirv.c_str(), SPV_ENV_VULKAN_1_1_SPIRV_1_4);
  ASSERT_EQ(SPV_ERROR_INVALID_ID,
            ValidateInstructions(SPV_ENV_VULKAN_1_1_SPIRV_1_4));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("OpCooperativeVectorReduceSumAccumulateNV V type <id> "
                        "'28[%v4half]' is not a cooperative vector type."));
}

TEST_F(ValidateMemory, CoopMatMatrixBFloatFAdd) {
  const std::string body =
      R"(
               OpCapability Shader
               OpCapability Float16
               OpCapability BFloat16TypeKHR
               OpCapability BFloat16CooperativeMatrixKHR
               OpCapability VulkanMemoryModel
               OpCapability CooperativeMatrixKHR
               OpExtension "SPV_KHR_bfloat16"
               OpExtension "SPV_KHR_vulkan_memory_model"
               OpExtension "SPV_KHR_cooperative_matrix"
               OpMemoryModel Logical Vulkan
               OpEntryPoint GLCompute %main "main" %_ %__0 %__1
               OpExecutionMode %main LocalSize 32 1 1
               OpDecorate %_arr_bfloat16_uint_64 ArrayStride 2
               OpDecorate %A Block
               OpMemberDecorate %A 0 Offset 0
               OpDecorate %_ Binding 0
               OpDecorate %_ DescriptorSet 0
               OpDecorate %_arr_bfloat16_uint_64_0 ArrayStride 2
               OpDecorate %B Block
               OpMemberDecorate %B 0 Offset 0
               OpDecorate %__0 Binding 1
               OpDecorate %__0 DescriptorSet 0
               OpDecorate %_arr_bfloat16_uint_64_1 ArrayStride 2
               OpDecorate %R Block
               OpMemberDecorate %R 0 Offset 0
               OpDecorate %__1 Binding 2
               OpDecorate %__1 DescriptorSet 0
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
   %bfloat16 = OpTypeFloat 16 BFloat16KHR
       %uint = OpTypeInt 32 0
     %uint_3 = OpConstant %uint 3
     %uint_8 = OpConstant %uint 8
     %uint_0 = OpConstant %uint 0
         %12 = OpTypeCooperativeMatrixKHR %bfloat16 %uint_3 %uint_8 %uint_8 %uint_0
%_ptr_Function_12 = OpTypePointer Function %12
    %uint_64 = OpConstant %uint 64
%_arr_bfloat16_uint_64 = OpTypeArray %bfloat16 %uint_64
          %A = OpTypeStruct %_arr_bfloat16_uint_64
%_ptr_StorageBuffer_A = OpTypePointer StorageBuffer %A
          %_ = OpVariable %_ptr_StorageBuffer_A StorageBuffer
        %int = OpTypeInt 32 1
      %int_0 = OpConstant %int 0
%_ptr_StorageBuffer_bfloat16 = OpTypePointer StorageBuffer %bfloat16
%_arr_bfloat16_uint_64_0 = OpTypeArray %bfloat16 %uint_64
          %B = OpTypeStruct %_arr_bfloat16_uint_64_0
%_ptr_StorageBuffer_B = OpTypePointer StorageBuffer %B
        %__0 = OpVariable %_ptr_StorageBuffer_B StorageBuffer
     %v3uint = OpTypeVector %uint 3
    %uint_32 = OpConstant %uint 32
     %uint_1 = OpConstant %uint 1
         %35 = OpConstantComposite %v3uint %uint_32 %uint_1 %uint_1
%_arr_bfloat16_uint_64_1 = OpTypeArray %bfloat16 %uint_64
          %R = OpTypeStruct %_arr_bfloat16_uint_64_1
%_ptr_StorageBuffer_R = OpTypePointer StorageBuffer %R
        %__1 = OpVariable %_ptr_StorageBuffer_R StorageBuffer
       %main = OpFunction %void None %4
          %6 = OpLabel
       %matX = OpVariable %_ptr_Function_12 Function
       %matY = OpVariable %_ptr_Function_12 Function
         %23 = OpAccessChain %_ptr_StorageBuffer_bfloat16 %_ %int_0 %uint_0
         %24 = OpCooperativeMatrixLoadKHR %12 %23 %int_0 %uint_8 None
               OpStore %matX %24
         %30 = OpAccessChain %_ptr_StorageBuffer_bfloat16 %__0 %int_0 %uint_0
         %31 = OpCooperativeMatrixLoadKHR %12 %30 %int_0 %uint_8 None
               OpStore %matY %31
         %32 = OpLoad %12 %matX
         %33 = OpLoad %12 %matY
         %34 = OpFAdd %12 %32 %33
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("FAdd doesn't support BFloat16 type"));
}

TEST_F(ValidateMemory, CoopMatMatrixFloat8FAdd) {
  const std::string body =
      R"(
               OpCapability Shader
               OpCapability Float8EXT
               OpCapability Float8CooperativeMatrixEXT
               OpCapability VulkanMemoryModel
               OpCapability CooperativeMatrixKHR
               OpExtension "SPV_EXT_float8"
               OpExtension "SPV_KHR_cooperative_matrix"
               OpExtension "SPV_KHR_vulkan_memory_model"
               OpMemoryModel Logical Vulkan
               OpEntryPoint GLCompute %main "main"
               OpExecutionMode %main LocalSize 32 1 1
               OpDecorate %gl_WorkGroupSize BuiltIn WorkgroupSize
       %void = OpTypeVoid
          %4 = OpTypeFunction %void
    %fp8e4m3 = OpTypeFloat 8 Float8E4M3EXT
       %uint = OpTypeInt 32 0
     %uint_3 = OpConstant %uint 3
    %uint_16 = OpConstant %uint 16
     %uint_0 = OpConstant %uint 0
         %12 = OpTypeCooperativeMatrixKHR %fp8e4m3 %uint_3 %uint_16 %uint_16 %uint_0
%_ptr_Function_12 = OpTypePointer Function %12
     %v3uint = OpTypeVector %uint 3
    %uint_32 = OpConstant %uint 32
     %uint_1 = OpConstant %uint 1
%gl_WorkGroupSize = OpConstantComposite %v3uint %uint_32 %uint_1 %uint_1
       %main = OpFunction %void None %4
          %6 = OpLabel
       %matR = OpVariable %_ptr_Function_12 Function
       %matX = OpVariable %_ptr_Function_12 Function
       %matY = OpVariable %_ptr_Function_12 Function
         %16 = OpLoad %12 %matX
         %18 = OpLoad %12 %matY
         %19 = OpFAdd %12 %16 %18
               OpStore %matR %19
               OpReturn
               OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_3);
  ASSERT_EQ(SPV_ERROR_INVALID_DATA, ValidateInstructions(SPV_ENV_VULKAN_1_3));
  EXPECT_THAT(getDiagnosticString(),
              HasSubstr("FAdd doesn't support FP8 E4M3/E5M2 types"));
}

TEST_F(ValidateMemory, PhysicalStorageBufferArray) {
  const std::string body =
      R"(
              OpCapability Shader
              OpCapability Int64
              OpCapability PhysicalStorageBufferAddresses
              OpExtension "SPV_KHR_storage_buffer_storage_class"
              OpExtension "SPV_KHR_physical_storage_buffer"
              OpMemoryModel PhysicalStorageBuffer64 GLSL450
              OpEntryPoint GLCompute %18 "main"
              OpExecutionMode %18 LocalSize 1 1 1
              OpSource OpenCL_C 120
              OpMemberDecorate %S 0 Offset 0
              OpMemberDecorate %S 1 Offset 256
              OpDecorate %array ArrayStride 16
      %uint = OpTypeInt 32 0
      %void = OpTypeVoid
     %ulong = OpTypeInt 64 0
     %float = OpTypeFloat 32
   %v4float = OpTypeVector %float 4
        %17 = OpTypeFunction %void
   %ulong_0 = OpConstant %ulong 0
    %uint_3 = OpConstant %uint 3
     %array = OpTypeArray %v4float %uint_3
         %S = OpTypeStruct %array %uint
     %ptr_S = OpTypePointer PhysicalStorageBuffer %S
   %float_0 = OpConstant %float 0
 %v4float_0 = OpConstantComposite %v4float %float_0 %float_0 %float_0 %float_0
        %23 = OpConstantComposite %array %v4float_0 %v4float_0 %v4float_0
        %24 = OpConstantComposite %S %23 %uint_3
        %18 = OpFunction %void None %17
        %19 = OpLabel
        %58 = OpConvertUToPtr %ptr_S %ulong_0
              OpStore %58 %24 Aligned 4
              OpReturn
              OpFunctionEnd
)";

  CompileSuccessfully(body.c_str(), SPV_ENV_VULKAN_1_0);
  ASSERT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_0));
}

}  // namespace
}  // namespace val
}  // namespace spvtools
